Gradient-free fine-tuning for any HuggingFace model. EGGROLL Evolution Strategies in PyTorch.
Project description
███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██╔════╝██╔════╝ ██╔════╝ ██╔══██╗██╔═══██╗██║ ██║ █████╗ ██║ ███╗██║ ███╗██████╔╝██║ ██║██║ ██║ ██╔══╝ ██║ ██║██║ ██║██╔══██╗██║ ██║██║ ██║ ███████╗╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝███████╗███████╗ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝
Gradient-Free Fine-Tuning for Any HuggingFace Model
PyTorch implementation of EGGROLL (Evolution Strategies at the Hyperscale, NVIDIA + Oxford).
No backprop. No gradients. Just evolution.
Why EGGROLL?
Backpropagation requires differentiable objectives, massive memory for activations, and complex distributed training setups. EGGROLL replaces all of that with evolution — mutate, evaluate, keep what works.
| Backprop (LoRA/GRPO) | EGGROLL | |
|---|---|---|
| Gradients needed | Yes | No |
| Memory (activations) | O(layers) | O(1) |
| Differentiable reward | Required | Any function |
| Works on quantized models | Limited | Native |
| Throughput | Training speed | ~91% of inference speed |
Install
pip install eggroll-es
Or from source:
git clone https://github.com/ShipItAndPray/eggroll.git
cd eggroll
pip install -e ".[dev]"
Quick Start
CLI — One Command
# Evolve GPT-2 to minimize perplexity
eggroll tune gpt2 --reward perplexity --generations 50
# Evolve Llama with custom reward, only attention layers
eggroll tune meta-llama/Llama-3.1-8B \
--reward score.py \
--target-modules q_proj v_proj \
--population 128 \
--rank 8
# Use an inline lambda as reward
eggroll tune gpt2 --reward "lambda text: 1.0 if 'yes' in text else 0.0"
# Show model info + EGGROLL memory estimates
eggroll info meta-llama/Llama-3.1-8B
Python API
from transformers import AutoModelForCausalLM, AutoTokenizer
from eggroll import EggrollTrainer, EggrollConfig, PerplexityReward
model = AutoModelForCausalLM.from_pretrained("gpt2")
tokenizer = AutoTokenizer.from_pretrained("gpt2")
config = EggrollConfig(
population_size=64, # mutations per generation
rank=8, # low-rank perturbation rank
sigma=0.01, # noise magnitude
lr=0.001, # learning rate
generations=100, # evolution steps
target_modules=["c_attn", "c_proj"], # only evolve these layers
)
reward = PerplexityReward(tokenizer)
trainer = EggrollTrainer(model, config, reward, tokenizer)
trainer.evolve(dataloader)
trainer.save("./evolved-model")
Custom Reward Functions
The killer feature — optimize for anything, not just differentiable losses:
from eggroll import EggrollTrainer, EggrollConfig, TextReward, CustomReward
# Score generated text (non-differentiable!)
def my_scorer(text: str) -> float:
if "correct answer" in text:
return 1.0
return 0.0
reward = TextReward(tokenizer, scorer=my_scorer)
# Or use any function of (model, inputs) -> float
reward = CustomReward(lambda model, inputs: run_tests(model, inputs))
# Combine multiple rewards
from eggroll import MultiReward
reward = MultiReward([
(PerplexityReward(tokenizer), 0.3),
(TextReward(tokenizer, code_scorer), 0.7),
])
How It Works
Based on NVIDIA + Oxford's EGGROLL paper:
- Mutate — Generate low-rank perturbations of model weights (A × B.T instead of full-rank noise)
- Evaluate — Run each mutated model on your reward function
- Select — Fitness-weighted combination of best mutations updates the parameters
- Repeat — Each generation gets closer to optimal
Generation 0 → Generation N
θ₀ θ*
┌──── mutate ────┐
│ θ + ε₁ → 0.3 │ Rank perturbations:
│ θ + ε₂ → 0.8 │ ε = σ · A · Bᵀ / √r
│ θ + ε₃ → 0.1 │
│ θ + ε₄ → 0.9 │ Update:
└──── select ────┘ θ ← θ + lr · Σ fᵢεᵢ / nσ
↓
θ + weighted avg
Why low-rank? Full-rank ES requires O(D) memory per population member. EGGROLL uses O(2Dr) where r << D, achieving 100x speedup while the approximation error drops as O(1/r).
Configuration
| Parameter | Default | Description |
|---|---|---|
population_size |
256 | Mutations per generation (higher = better gradient estimate) |
rank |
8 | Low-rank perturbation rank (higher = more accurate, more memory) |
sigma |
0.01 | Noise magnitude (too high = chaos, too low = no exploration) |
lr |
0.001 | Learning rate for parameter updates |
generations |
100 | Number of evolution steps |
antithetic |
True | Mirror perturbations to halve variance |
fitness_shaping |
"centered_rank" | "centered_rank", "normalized", or "raw" |
target_modules |
None | Only evolve layers matching these patterns |
elite_k |
0 | Keep only top-k members (0 = use all) |
weight_decay |
0.0 | L2 regularization |
CLI Reference
eggroll tune MODEL [OPTIONS]
MODEL HuggingFace model ID or local path
--reward, -r REWARD perplexity, path/to/score.py, or lambda
--generations, -g N Evolution generations (default: 100)
--population, -p N Population size (default: 64)
--rank N Low-rank perturbation rank (default: 8)
--sigma F Noise std dev (default: 0.01)
--lr F Learning rate (default: 0.001)
--output, -o DIR Output directory
--dataset, -d DATASET HuggingFace dataset (default: wikitext-2)
--target-modules M [M..] Only evolve matching layers
--seed N Random seed (default: 42)
eggroll info MODEL Show model info + memory estimates
Custom Reward File Format
Create a Python file with either:
# Option 1: Score generated text
def score(text: str) -> float:
return 1.0 if "correct" in text else 0.0
# Option 2: Full model access
def reward_fn(model, inputs) -> float:
output = model(**inputs)
return -output.loss.item()
Then: eggroll tune gpt2 --reward my_reward.py
Comparison to Existing EGGROLL Implementations
| This library | HyperscaleES (official) | egg.c | eggroll-embedding-trainer | |
|---|---|---|---|---|
| Language | PyTorch | JAX | CUDA/C | PyTorch |
| HuggingFace integration | Yes | No | No | No |
| CLI | Yes | No | No | No |
| Custom rewards | Any function | Hardcoded | Hardcoded | NDCG only |
| Install | pip install | Manual | Compile | Manual |
| Use case | General fine-tuning | Research | Edge/embedded | Retrieval |
Development
pip install -e ".[dev]"
pytest tests/ -v
Paper
Gajane et al. "Evolution Strategies at the Hyperscale" (2025) NVIDIA + University of Oxford + MILA arxiv.org/abs/2511.16652 | Project Page
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eggroll_es-0.1.0.tar.gz.
File metadata
- Download URL: eggroll_es-0.1.0.tar.gz
- Upload date:
- Size: 20.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb0a1c17a2dadc5958f135f7077902bb87cbe4c019818550c78699280142ec28
|
|
| MD5 |
2568df129e5455a97313fba77e298773
|
|
| BLAKE2b-256 |
f55e3741694146f4f0d5ce6257f3577525e1ec5d4496b5cff1c580749fb0669b
|
File details
Details for the file eggroll_es-0.1.0-py3-none-any.whl.
File metadata
- Download URL: eggroll_es-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
905cf9bb5855db35e7246b4487829f5d12dd0a107908a3fa5ff243e1ef145e34
|
|
| MD5 |
d4d48780da0d344e41ff94db0b047cf2
|
|
| BLAKE2b-256 |
ed37ab87437062f00ccf98b0cf44aa09d2229c007690bf7553e9d69a2c6c895d
|