01

Tiny Model,
Big Jobs.

Optimizing LLMs with DSPy + GEPA

But first — who am I?

Arun Prasath E G — 20+ years · co-founder, eight times over.

CO-FOUNDER · CTO · IN
Retalp
CO-FOUNDER · CTO · US
flashkut.ai
CO-FOUNDER · CTO · EU
Traderdo
CO-FOUNDER · CTO · EU
Sawant Clark
CO-FOUNDER · CTO · UK
theisland.app
CO-FOUNDER · CTO · UK
ralu.tech
CO-FOUNDER · CTO · EU
doplace
CO-FOUNDER · VP · US
Clientown
ENGINEERING HEAD · US
Myprogrammer
DEVELOPMENT MANAGER · IN
ABCO
SENIOR ENGINEER · IN
Honeywell

Sounds like an executive. I'm a builder.

7,200+contributions in the last 365 days

Writing a prompt is easy.
Making it reliable isn't.

Where we're headed — five moves.

From hand-tuned guesswork to a prompt you compile. Here's the whole path — we'll walk it end to end.

01 · THE PROBLEM
Prompts break
hand-written, no metric, no data
02 · THE TASK
One robot, one score
the job, the score — and inside the 8B brain
03 · THE FRAMEWORK
DSPy
program the prompt, don't write it
04 · THE OPTIMIZER
GEPA
it writes the prompt from failures
05 · THE PAYOFF
Tiny model wins
8B beats a model 30× its size

Five parts — each one earns the next. By the end, you'll have the whole recipe.

Part 1 · The problem
The hand-written way
Just you, the words, and hope.

Recognise this?

01
Tweak the wording
+ "be precise."
02
Run it once
✓ looks perfect
03
Screenshot the good output
"see — it works!"
04
Ship it, and pray
push to prod
05
It breaks on a new input
weird input → ✗

No metric. No data. Just vibes.

Part 2 · The task
One robot,
one score.
To fix reliability, first we have to measure it. A real job, a pass/fail we can trust — and a proper look at the tiny brain doing it.

The test: a warehouse robot

Let's find out how good the hand-written way really is — with a number, not a vibe.

Put the vase into bin_1.

The brain doing it: a tiny 8B — small enough to live on the robot.

Sounds trivial. One score: did it finish — intact?

Detour · the basics

You already know a model.

Before the 8B: what is a model? Celsius in, Fahrenheit out — you've used this one since school. Now look at where the numbers live.

Foutput = 1.8parameter №1 × Cinput + 32parameter №2
25°C → 77°F 100°C → 212°F −40°C → −40°F same two constants — every single input

The temperature flows through. The 1.8 and the 32 stay. Those two numbers are the model.

Detour · the basics

Nobody told it 1.8.

Hide the constants. Hand it real thermometer readings instead. Then loop: guess, check, nudge. Watch.

watch it learn — livesped up · 2,000 steps
# start from random dials F = 0.42 × C + 7.0 example: 25°C → should be 77°F model says: 17.5°F wrong by −59.5 step 1 / 2,000 — nudging both dials…
1 · GUESS
run the formula on an example
0.42 × 25 + 7.0 = 17.5
2 · CHECK
compare with the truth
should be 77 → off by −59.5
3 · NUDGE
turn both dials a hair toward less wrong
w ↑ · b ↑ · go again

Big error → big nudge. Tiny error → tiny nudge. The dials settle where the error dies. An LLM is this exact loop — with 8,000,000,000 dials.

Detour · the basics

8B? Eight billion what?

A language model is one giant equation with a single job: guess the next word. A parameter is one number inside it — one tiny dial.

the model, from outside8,000,000,000 dials — but what ARE they?the box, opened flat — a table between words
"the vase is ___" 8,000,000,000 dials inside every one set by training fragile 0.71 heavy 0.18 blue 0.05
rows: your words  ·  columns: every possible next word
"the vase is ___"fragileheavyblue…every other word
the−0.27−0.31−0.24
vase0.83−0.52−1.87
is0.100.120.12
add the column ↓0.66−0.71−1.99
▪ 0.83 = one cell = one parameter

Biggest total wins: fragile 0.66 → 71%. 8 billion = crossings between words — not words.

Detour · the basics

Why dials cost gigabytes

Eight billion learned dials — and every one has to sit in memory, loaded and ready, about two bytes each. Now just multiply.

the brains, drawn to scalearea = parameter count
8B — ours · ~16 GB · rides on the robot 235B — the giant 30× ours · ~470 GB server-only ≈1T — frontier-class 125× ours · ~2 TB of dials 1,000,000,000,000 learned numbers
THE ARITHMETIC
8 billion dials × 2 bytes each
8,000,000,000 × 2 B = 16 GB
WHY BIG = SLOW + COSTLY
every token sweeps the network — all dials loaded, always
more GB → more ms → more ₹ per token
SIZE PICKS YOUR HARDWARE
16 GB rides a robot · 2 TB needs a rack
8B on-device ✓ · 1T datacenter + network

So 8B isn't a compromise — it's the biggest brain the robot can carry.

Detour · the basics

Then why not a 1B? Or 100M?

Fair question — smaller is lighter and faster still. But dials are capacity: language, patterns, the ability to follow instructions. Shrink too far and the model loses the plot.

model size — where brains livelog scale
too small — loses the plot mid-task brilliant — chained to a rack the sweet spot 100M 1B 8B 70B 235B 1T
WHAT SHRINKS FIRST
instruction-following & multi-step focus
great autocomplete · lost in a loop
8B — THE EDGE SWEET SPOT
the smallest brain that holds the plot, step after step
follows the rules ✓ · 16 GB ✓ · on-robot ✓
AND ABOVE 8B?
smarter, yes — but the gains move to the rack
size sets the floor · words set the ceiling

Now — how the world works

The robot moves between locations, and the world talks back. Note the wrap_station — the vase is fragile. A brain working a world like this has a name: an agent.

the warehouse — top-down maplocations hold objects
shelf_A vase (fragile) wrap_station a place you can wrap things entrance crate robot bin_1 ← the goal (empty)
The rules — the ones written down
You go to a location — never straight to an object.
To pick something up, be at its location first.
The gripper holds one thing at a time.
The only valid actions
goto <location> pick <object> place <object> in <location> wrap <object> · only at wrap_station
Detour · the basics

But what is an agent?

One vase, four zones, four actions. But this is not one question with one answer — it's a goal that takes many steps, in a world that reacts to every move.

A single call
Ask once. Done.
task LLM action
One input, one output. Right or wrong, it's over — it never sees what happened next.
An agent
See. Decide. Act. Again.
see decide act
It sees, decides, acts — then sees what changed, and goes again until the goal is met.

Same model. The difference is the loop — and a goal it keeps working toward.

Detour · the basics

The agent loop — see, decide, act.

Every step, our robot runs the same three-beat cycle — and only one beat is a prompt.

one step of the looprepeats until done
THE GOAL vase → bin_1 1 SEE 2 DECIDE 3 ACT
1 · SEE
read what the world shows
at shelf_A · see vase (fragile)
2 · DECIDE — the policy
pick the next action
policy(obs) → "pick vase"
← the only part that's a prompt
3 · ACT
the world runs it, and changes
env.step(action) → a new observation

The brain — how it decides — is just a prompt. Make that reliable, and the whole agent gets reliable.

The hand-written prompt

That decide step? Today it's just this — a wall of hand-written rules. Start simple; each new failure adds a line.

prompt.txt — and growingplain text
You are a warehouse robot. Complete the task. Reply with ONE action each step. Task: {task} · You see: {observation}
+ One action only: goto, pick, place, wrap.↯ invented an action
+ goto an object's shelf before you pick it.↯ walked to a box
+ The gripper holds ONE — place before picking.↯ grabbed two
+ Never repeat an action that just failed.↯ looped
+ Put objects only in their target bin.↯ wrong bin
+ Don't stop until the task is done.↯ quit early
Each is a failure you hit. Each + is your patch.
It never ends — you maintain this wall, by hand, forever.

Now run your wall of rules.

All 20 tasks, logged. And it still breaks — on the one rule you never thought to write.

0%
6 of 20 tasks solved.
fragile tasks survived: 0 of 11 · sturdy solved: 6 of 9
Your prompt never said "wrap fragile items."
Why would it? You didn't know to.

"Just use a bigger model?"

The obvious fix. So we tried it — 30× more parameters, the exact same prompt.

0%
30× bigger — the exact same prompt
~4× the cost per task · cloud-only
sturdy logistics: a perfect 9/9 — capacity works
fragile tasks: 0/11 — every single one shattered

Bigger bought 15 points — at 4× the price. The words are still the problem. So let's fix the words.

Programming, not prompting.

By hand
You write the exact words and hope they generalize. When it breaks, you reword and pray.
As a program
You declare the goal and a way to score it — and let an optimizer write the words.

There's a framework built for exactly this. Let's build it — piece by piece.

Part 3 · The framework
DSPy
Declare what you want. It writes — and improves — the prompt.

What is DSPy?

Declarative Self-improving Python — from Stanford. A framework for programming language models, not prompting them.

SIGNATURE
declare the inputs → outputs you want
MODULE
the strategy that produces them
OPTIMIZER
improves the prompt from your data

Three pieces, in order: Signature → Module → Optimizer. We'll build them one at a time.

Signature — declare the task

Not a prompt — a typed spec: what goes in, what comes out. DSPy turns it into the prompt.

a DSPy signaturepython
class NextAction(dspy.Signature): """Pick the next action to finish the task.""" # ← becomes the instruction task = dspy.InputField() observation = dspy.InputField(desc="what the robot sees") valid_actions = dspy.InputField(desc="the moves allowed now") next_action = dspy.OutputField()
Inputs → outputs. Like a function signature, for the model.
The docstring + field names become the prompt — you never write "You are a helpful…".
Shorthand too: dspy.Predict("question -> answer").

Module — the strategy

A module wraps a signature and decides how to get the answer. Swap the module, change how it thinks.

Predict
ask the model directly
basic
ChainOfThought
reason first, then answer
reasoning
ReAct
use tools, in a loop
agent
ProgramOfThought
write & run code
code
Refine · BestOfN
try several, keep the best
ensemble

We'll look at the first three — then pick one for our robot.

Predict — just answer

The simplest module: inputs in, output out — no reasoning, no tools.

dspy.Predict — what a single run looks likerun · 1 of 3
>>> policy = dspy.Predict("task, observation, valid_actions -> next_action") >>> policy(task="put the crate in bin_1", observation="at entrance · see crate · hands empty", valid_actions="pick crate | goto shelf_A | goto bin_1") next_action: pick crate

Straight to the answer — no working shown, and nothing to catch a wrong move.

ChainOfThought — think, then answer

Same signature. DSPy quietly adds a reasoning field — the model works it out before it answers.

dspy.ChainOfThought — same input, one new outputrun · 2 of 3
>>> policy = dspy.ChainOfThought("... -> next_action") >>> policy(task="put the crate in bin_1", observation="at entrance · see crate · hands empty", valid_actions="pick crate | goto shelf_A | goto bin_1") reasoning: the crate is right here and my hands are empty, so the next move is to pick it up. next_action: pick crate

That new reasoning line is the whole difference — it thinks before it acts, so it's far more reliable on anything with steps.

ReAct — reason, act, repeat

An agent: it reasons, calls a tool, reads the result, and loops until the job is done.

dspy.ReAct — a tool-using looprun · 3 of 3
>>> agent = dspy.ReAct("task -> done", tools=[goto, pick, place, wrap]) >>> agent(task="put the crate in bin_1") thought: the crate is right here at the entrance. act: pick("crate") obs: holding crate act: goto("bin_1") obs: at bin_1 · holding crate act: place("crate", "bin_1") obs: done ✓

Remember the agent loop — see, decide, act? ReAct is exactly that, built into one module. (We keep ours explicit, so it's clear what the optimizer tunes.)

Step one: make it a program

The same task — now a typed Signature, run by a Module, one step at a time.

Typed inputs and outputs — not one blob of text.
It now sees valid_actions, so it stops inventing commands.
ChainOfThought = reason, then act. (ReAct is its tool-using cousin.)
Still 30% — but now it's a program an optimizer can rewrite.
policy.pypython
class NextAction(dspy.Signature): """Pick the next action to finish the task.""" task = dspy.InputField() observation = dspy.InputField() valid_actions = dspy.InputField() history = dspy.InputField() next_action = dspy.OutputField() policy = dspy.ChainOfThought(NextAction) # reason, then act # act one step at a time — env is the warehouse world hist = [] while not env.done: step = policy(task=task, observation=obs, valid_actions=env.actions(), history=hist[-8:]) obs = env.step(step.next_action) hist.append(step.next_action + " → " + obs)

Now tell it why it failed

Optimizing needs a score. The best optimizers want more — the reason behind it.

metric.py — score, plus a reasonpython
def metric(example, result): if result.success: return dspy.Prediction(score=1.0, feedback="Solved.") return dspy.Prediction(score=0.0, feedback=diagnose(result)) # on a failure, the optimizer reads: "The vase shattered — it was placed in bin_1 while still unwrapped."
Most optimizers see only the score.
A good optimizer also reads the feedback — the why.
A number says it failed. The reason says how to fix it.

Optimizer — improve it from data

Hand it your module + a metric + a few examples. It rewrites the prompt to score higher — you never touch the words.

optimize the policypython
# your module + a metric + a few episodes policy = dspy.ChainOfThought(NextAction) optimizer = dspy.GEPA(metric=metric, reflection_lm=teacher) optimized = optimizer.compile(policy, trainset=TRAIN, valset=VAL) # you never touch the words
It tunes the instructions and the examples — learned from data, not by hand.
DSPy ships several: BootstrapFewShot, MIPROv2…
Ours is GEPA. Here's how it works →
Part 4 · The optimizer
GEPA
Let it rewrite the prompt — from its own failures.

What is GEPA? — a learning loop.

Genetic-Pareto — it evolves prompts from their own failures, automatically. From the 2025 paper "Reflective Prompt Evolution Can Outperform Reinforcement Learning."

01 · TRY
Run it on a few tasks
02 · READ
See why it failed
03 · REFLECT
A teacher model rewrites the prompt
04 · KEEP
Better? Saved. Worse? Gone.

40% → 95%, one loop at a time.

The run itself — replayed.

The recorded run, in six partsit pauses at each milestone; we talk, then continue.

What GEPA wrote — that you didn't.

policy.signature.instructions — after optimizationexcerpts · full text next →
You are a warehouse robot tasked with moving objects into bin_1. If an object is marked as fragile, go to the wrap_station immediately after picking it up. Wrap the fragile object before proceeding to bin_1 so it doesn't shatter. Ensure ALL requested objects are delivered to bin_1. Never carry fragile objects without wrapping them first.
Nobody typed these rules.
GEPA induced them from the failures it read.
A big teacher model helped — once, at compile time. What ships is the tiny 8B, alone.

The full prompt — unedited.

One line of code. Thirty-two lines of instructions nobody typed.

Why keep a 40% prompt? Watch the record board.

GEPA keeps a score for every candidate on every task — the 20 val tasks, side by side.

1234567891011121314151617181920
candidate 1 · 95%
candidate 0 · 40%

Task #2 — the champion's only ✗. Candidate 0 holds the only ✓ → unbeaten there → it stays. That's Pareto. "Put blue_box, red_box and green_box into bin_1"

Staying = quizzed again — and its failures write candidate 2 · 95%. That's Genetic.

Winners stop failing. Losers keep teaching. One prompt ships.

Same task. Now it works.

the route — simplified mapsolved in 7 steps
shelf_A wrap_station wrap the vase here first entrance crate bin_1 the goal
Solved — and intact. ✓

The prompt did all the work.

Hand-writtensmall 8B · the edge model
0%
Bigger model30× larger · same prompt
0%
Same 8B + GEPAlearned prompt
0%

Bigger bought 15 points (45%). Better words bought 65 points (95%) — on the same tiny 8B, at a fraction of the cost, small enough to ride the robot. You never wrote the winning prompt.

measured on 20 held-out tasks

Part 5 · The payoff
Same model.
New prompt.
Nobody wrote the winning prompt by hand — the optimizer found the words.

Same dials. Different light.

Remember the box of dials? They've been frozen since training — GEPA never touched one. The only thing that changed is which ones fire.

the same brain, twice — dial for dialonly the input words differ
your prompt — hand-written the prompt GEPA wrote 30% the vase shatters 95% solved — intact

Fine-tuning turns the dials. Prompting chooses which ones fire. GEPA automates the search for the words that light the right ones.

It's still just a prompt, right?

Right — but remember the treadmill: every rule you hand-bolt can silently break the one before. Developers fixed this long ago — Test-Driven Development.

TDD · step 1
A requirement = a test.

Write it before the code — it fails. "Working" now has a definition.

TDD · step 2
Build until it passes.

Write code until the test goes green.

+
TDD · step 3
Add the next one — break nothing.

New requirement? Add its test, build on top — the old tests prove nothing broke.

for prompts

A requirement = tasks + a metric. "Handle fragile items" → 20 tasks. Hand prompt: 30% — a fact, not a feeling.

for prompts

GEPA writes the words until the metric climbs — 95%.

for prompts

New requirement? Add its tasks, recompile — the old 20 prove the vase still survives.

Every requirement becomes a test. Every change ships with proof. New rule, new model, new task — recompile, and know.

The robot was just the demo.

The method works anywhere an LLM has a job — and you can score it.

AGENTS
tool use & planning
RAG
answer from sources
CLASSIFY
labels, routing, triage
EXTRACT
messy text → clean data

If you can score it, GEPA can optimize it.

The whole recipe — on one slide.

Everything we just built, in four moves. This is DSPy + GEPA, end to end.

01 · SIGNATURE
Declare the task
typed inputs → outputs
02 · MODULE
Pick how it thinks
Predict · ChainOfThought · ReAct
03 · METRIC
Score it — with a reason
did it finish? why not?
04 · OPTIMIZE
Let GEPA write it
the prompt tunes itself

You declare the intent and a way to score it. The optimizer writes the words — and keeps only the ones that win.

That's the whole idea
Thank you.
Stop tuning prompts. Compile them.
30 seconds of feedbackscan · or https://tinymodel.apeg.dev/fslides + notebook: github.com/arun-prasath2005/tiny-model-big-jobs