A minimal PyTorch-based training and inference framework for small language models with reinforcement learning from human feedback (RLHF). Supports DPO (Direct Preference Optimization) and GRPO (Group Relative Policy Optimization) training modes.
- ~1M parameter transformer model runnable on CPU
- Modern architecture: RMSNorm, Rotary Position Embeddings (RoPE), SwiGLU activation
- DPO training: Direct preference optimization without reward modeling
- GRPO training: Group relative policy optimization (DeepSeek-R1 style) without critic networks
- Character-level tokenizer: No external dependencies, works out of the box
- Simple data format: JSON Lines for easy data preparation
- Minimal dependencies: Only requires PyTorch
# Clone or navigate to the project
cd mini-rl
# Install dependencies
pip install -r requirements.txtRequirements:
- Python 3.8+
- PyTorch >= 2.0
python train.py --mode dpo --data data/dpo_sample.jsonl --epochs 10 --output dpo_model.ptpython train.py --mode grpo --data data/grpo_sample.jsonl --epochs 10 --output grpo_model.pt# Single prompt
python inference.py --checkpoint model.pt --prompt "Hello, how are you?"
# Interactive chat mode
python inference.py --checkpoint model.pt
# With custom generation settings
python inference.py --checkpoint model.pt --prompt "Hello" \
--max-tokens 100 --temperature 0.8 --top-p 0.9JSON Lines format with preference pairs:
{"prompt": "What is 2+2?", "chosen": "2+2 equals 4.", "rejected": "2+2 equals 5."}
{"prompt": "What color is the sky?", "chosen": "The sky is blue.", "rejected": "The sky is green."}JSON Lines format with grouped responses and rewards:
{"prompt": "What is 3*4?", "responses": ["12", "The answer is 12.", "11"], "rewards": [1.0, 0.9, 0.0]}
{"prompt": "Say hello:", "responses": ["Hello!", "Hi there!", "Goodbye!"], "rewards": [1.0, 0.9, 0.1]}The model follows a GPT-style decoder-only transformer with modern improvements:
| Component | Description |
|---|---|
| Normalization | RMSNorm (Root Mean Square Layer Normalization) |
| Position Encoding | Rotary Position Embeddings (RoPE) |
| Activation | SwiGLU in feed-forward layers |
| Attention | Multi-head causal self-attention |
| Parameter | Value |
|---|---|
| Vocabulary Size | 260 (256 ASCII + 4 special tokens) |
| Hidden Dimension | 128 |
| Number of Layers | 4 |
| Attention Heads | 4 |
| Feed-forward Dimension | 512 |
| Max Sequence Length | 512 |
| Dropout | 0.1 |
--mode Training mode: dpo or grpo (required)
--data Path to training data JSONL (required)
--eval-data Path to evaluation data JSONL (optional)
--epochs Number of training epochs (default: 10)
--batch-size Batch size (default: 8)
--lr Learning rate (default: 1e-4)
--device Device: cpu or cuda (default: cpu)
--output Output checkpoint path
--checkpoint Resume from checkpoint
--d-model Hidden dimension (default: 128)
--n-layers Number of transformer layers (default: 4)
--n-heads Number of attention heads (default: 4)
--d-ff Feed-forward dimension (default: 512)
--max-seq-len Maximum sequence length (default: 512)
--dropout Dropout rate (default: 0.1)
--beta KL penalty coefficient (default: 0.1)
--reference-free Use reference-free DPO variant
--label-smoothing Label smoothing factor (default: 0.0)
--group-size Number of responses per prompt (default: 4)
--clip-eps PPO clipping epsilon (default: 0.2)
--kl-coef KL divergence coefficient (default: 0.01)
--normalize-advantages Normalize advantages by std
--log-dir Directory for training logs (default: logs)
--experiment-name Name for this experiment (auto-generated if not provided)
--no-logging Disable training logging
--log-gradients Log gradient histograms (slower, for debugging)
--log-weights Log weight histograms (slower, for debugging)
--checkpoint Path to model checkpoint (required)
--prompt Input prompt (if omitted, enters chat mode)
--max-tokens Maximum new tokens to generate (default: 100)
--temperature Sampling temperature (default: 0.8)
--top-k Top-k sampling parameter (default: 50)
--top-p Top-p nucleus sampling (default: 0.9)
--repetition-penalty Repetition penalty (default: 1.1)
--stream Stream output token by token
Mini-RL includes a TensorBoard-style visualization dashboard for monitoring training progress.
# Start the visualization server (default port: 6006)
python visualize.py --log-dir logs
# Custom port and host
python visualize.py --log-dir logs --port 8080 --host 0.0.0.0
# Don't auto-open browser
python visualize.py --log-dir logs --no-browser- Scalar Charts: Interactive line charts for loss, accuracy, rewards, learning rate, and gradient norms
- Smoothing: Adjustable exponential moving average smoothing for cleaner visualizations
- Histograms: Parameter distribution histograms (when
--log-gradientsor--log-weightsis enabled) - Experiment Comparison: Select multiple experiments to compare side-by-side
- Configuration View: See hyperparameters for each experiment
- Auto-refresh: Automatically updates every 5 seconds during training
DPO Training:
train/loss: DPO losstrain/accuracy: Preference accuracy (chosen > rejected)train/chosen_rewards: Implicit reward for chosen responsestrain/rejected_rewards: Implicit reward for rejected responsestrain/reward_margin: Difference between chosen and rejected rewardstrain/lr: Learning ratetrain/gradient_norm: Total gradient normeval/*: Evaluation metrics (if eval data provided)
GRPO Training:
train/loss: GRPO losstrain/policy_loss: Policy gradient losstrain/kl_div: KL divergence from referencetrain/reward_mean: Average reward in grouptrain/reward_std: Reward standard deviationtrain/advantage_mean: Average advantagetrain/ratio_mean: Policy ratio meantrain/lr: Learning ratetrain/gradient_norm: Total gradient norm
from logger import TrainingLogger, LogReader
# Create logger
logger = TrainingLogger(log_dir="logs", experiment_name="my_experiment")
# Log configuration
logger.log_config(config)
# Log scalars
logger.log_scalar("train/loss", 0.5, step=100)
logger.log_scalars("train", {"loss": 0.5, "accuracy": 0.8}, step=100)
# Log histograms
logger.log_histogram("weights/layer1", tensor, step=100)
# Log model parameters
logger.log_model_params(model, step, log_gradients=True, log_weights=True)
# Close logger
logger.close()
# Read logs
reader = LogReader("logs")
experiments = reader.list_experiments()
scalars = reader.get_scalars("my_experiment")
config = reader.get_config("my_experiment")from config import ModelConfig
from model import create_model
# Default ~1M parameter model
model = create_model()
# Custom configuration
config = ModelConfig(
d_model=256,
n_layers=6,
n_heads=8,
d_ff=1024
)
model = create_model(config)
print(f"Parameters: {model.count_parameters():,}")from config import ModelConfig, DPOConfig
from model import create_model
from tokenizer import CharTokenizer
from dpo_trainer import DPOTrainer
from data import create_preference_dataloader
# Setup
model = create_model()
tokenizer = CharTokenizer()
config = DPOConfig(lr=1e-4, epochs=10, device="cpu")
# Create trainer
trainer = DPOTrainer(model, tokenizer, config)
# Load data and train
dataloader = create_preference_dataloader("data.jsonl", tokenizer)
history = trainer.train(dataloader)
# Save model
trainer.save_checkpoint("model.pt")from config import ModelConfig, GRPOConfig
from model import create_model
from tokenizer import CharTokenizer
from grpo_trainer import GRPOTrainer
from data import create_grpo_dataloader
# Setup
model = create_model()
tokenizer = CharTokenizer()
config = GRPOConfig(lr=1e-4, epochs=10, group_size=4)
# Create trainer
trainer = GRPOTrainer(model, tokenizer, config)
# Load data and train
dataloader = create_grpo_dataloader("data.jsonl", tokenizer)
history = trainer.train_offline(dataloader)from grpo_trainer import GRPOTrainer
from data import create_prompt_dataloader
def my_reward_fn(prompt: str, response: str) -> float:
# Custom reward logic
return 1.0 if "correct" in response.lower() else 0.0
trainer = GRPOTrainer(model, tokenizer, config, reward_fn=my_reward_fn)
prompt_loader = create_prompt_dataloader("prompts.jsonl", tokenizer)
trainer.train_online(prompt_loader)from inference import InferenceEngine
# Load from checkpoint
engine = InferenceEngine.from_checkpoint("model.pt")
# Generate text
output = engine.generate(
prompt="Hello",
max_new_tokens=50,
temperature=0.8,
top_p=0.9
)
print(output)mini-rl/
├── config.py # Configuration dataclasses
├── tokenizer.py # Character-level tokenizer
├── model.py # Transformer architecture
├── data.py # Dataset and DataLoader utilities
├── dpo_trainer.py # DPO training implementation
├── grpo_trainer.py # GRPO training implementation
├── inference.py # Text generation and CLI
├── train.py # Main training entry point
├── logger.py # Training logging utilities
├── visualize.py # TensorBoard-style dashboard
├── test_framework.py # Test suite
├── requirements.txt # Dependencies
├── logs/ # Training logs (created automatically)
└── data/
├── dpo_sample.jsonl # Sample DPO training data
└── grpo_sample.jsonl # Sample GRPO training data
Run the test suite to verify all components:
python test_framework.py- Pre-training required: DPO and GRPO are alignment methods that fine-tune existing language models. For meaningful text generation, pre-train the model on language modeling data first.
- CPU-friendly: The default 1M parameter model is designed to run efficiently on CPU for experimentation and learning.
- Scaling up: Increase
d_model,n_layers, andd_fffor larger models. Use--device cudafor GPU acceleration.
- DPO: Direct Preference Optimization
- DeepSeek-R1 Technical Report (GRPO)
- RoFormer: Enhanced Transformer with Rotary Position Embedding
- GLU Variants Improve Transformer (SwiGLU)
MIT License