In one of my previous posts, I explored GEPA (Genetic-Pareto), one of the advanced prompt optimization techniques available in DSPy.
Suppose you already have a working program and a metric (for evaluation). The next question is: which optimizer should you use to improve your program.
DSPy offers several optimization approaches, ranging from simple few-shot demonstration selection to evolutionary prompt optimization and model fine-tuning. Understanding their differences can help you choose the right approach without spending unnecessary time and compute.
What does each optimizer tune?
As illustrated in the figure at the beginning of this post, DSPy optimizers generally target one or more of three components:
- Demonstrations (demos): In-context examples included in the prompt.
- Instructions: Natural language instructions that guide the LLM.
- Model weights: The underlying parameters of the LLM.
The first two approaches optimize the prompt and can therefore work with virtually any LLM, including models accessed through APIs.
Weight optimization, on the other hand, requires a model whose weights can be fine-tuned, such as an open-source model or one supported by an LLM provider’s fine-tuning endpoint.
How to narrow down the options?
DSPy suggests considering two key questions when choosing an optimizer:
- What is the bottleneck? Instructions, demos, or model weights?
- What is your compute budget? Some optimizers require substantially more LLM calls than others.
The first question helps identify what needs optimization. If the prompt wording is the problem, instruction optimizers (COPRO, GEPA, MIPROv2) can help. If the model needs examples to anchor format and style, demo optimizers (BootstrapFewShot, BootstrapRS) are worth considering. If the model itself is the limit and you can fine-tune it, weight tuning (BootstrapFinetune) is the lever.
The second question determines how extensively you can search for improvements. Zero-config baselines (LabeledFewShot, BootstrapFewShot) cost almost nothing. Search-based optimizers (BootstrapRS, MIPROv2, GEPA) can require considerably more cost. Combined optimizers (BetterTogether) pay both bills.
In practice, however, there is one more important consideration:
How many labeled examples do you have?
The size and quality of your training dataset determine which optimization approaches are practical. With only a handful of labeled examples, sophisticated optimization can easily overfit, particularly when the same examples are used for both optimization and evaluation. There may also be insufficient successful examples to bootstrap useful demonstrations.
In this situation, start with a simple baseline, use demonstrations carefully, and consider generating additional labeled data. With a larger and more representative dataset, you can run more reliable evaluations and explore search-based optimizers with greater confidence. Fine-tuning generally requires a more substantial and suitable training dataset.
Remember that dataset size alone is not enough. Diversity, representativeness, label quality, and evaluation metric reliability matter just as much.
A closer look at each optimizer
Let’s start with optimizers that focus on demonstrations.
dspy.LabeledFewShot(k=16)
- A simple baseline to start with.
- No LLM calls during .comple()
- Samples up to k examples from the trainset, and use them as demos in the prompt
dspy.BootstrapFewShot(metric=None, metric_threshold=None, teacher_settings=None, max_bootstrapped_demos=4, max_labeled_demos=16, max_rounds=1)
- Another useful baseline.
- Automatically generates demonstrations from successful model predictions.
- Instead of manually writing examples into your prompt, DSPy lets the model solve training examples, evaluates its predictions using your metric, and keeps successful ones as bootstrapped demonstrations.
- The final demonstration set can contain up to max_bootstrapped_demos bootstrapped examples and max_labeled_demos original labeled examples.
dspy.BootstrapFewShotWithRandomSearch(metric, max_bootstrapped_demos=4, max_labeled_demos=16, max_rounds=1, num_candidate_programs=16, num_threads=None, stop_at_score=None)
- Extends BootstrapFewShot with randomized candidate search.
- Runs multiple BootstrapFewShot trials with different randomized demonstration selections.
- Evaluates the resulting candidate programs on a validation set and selects the best-performing one.
This is useful when a single BootstrapFewShot run does not produce consistently good demonstrations.
dspy.KNNFewShot(k, trainset, vectorizer, **bootstrap_kwargs)
- Demos are chosen dynamically at inference time
- Uses a k-nearest-neighbor approach to compare the input with training examples and retrieve the most relevant k demonstrations.
- Particularly useful when different inputs benefit from different examples and you have a sufficiently large, representative training set.
Next, let’s look at optimizers that focus on instructions.
dspy.COPRO(prompt_model=None, metric=None, breadth=10, depth=3, init_temperature=1.4, track_stats=False)
- A breadth-and-depth instruction search optimizer.
- At each depth level, generates multiple candidate instructions for each predictor and evaluates them, retaining the better-performing candidates.
- Useful when your existing demonstrations are already strong, but the instruction wording needs improvement.
dspy.GEPA(metric, auto=None, max_full_evals=None, max_metric_calls=None, reflection_lm=None, skip_perfect_score=True, instruction_proposer=None, use_merge=True, num_threads=None)
- An evolutionary optimizer that uses natural-language feedback and reflection to improve programs.
- GEPA maintains a population of candidate programs, evaluates them on the training examples, and uses feedback from the metric to identify opportunities for improvement.
- A reflection LLM (reflection_lm) analyzes the feedback and proposes instruction edits. GEPA also uses Pareto-based selection to retain promising candidates that perform well across different examples.
For a deeper explanation of how GEPA works, see my previous post: Demystifying GEPA: Genetic-Pareto Prompt Optimizer.
The following optimizers combine instruction and demo optimization.
dspy.MIPROv2(metric, prompt_model=None, task_model=None, teacher_settings=None, max_bootstrapped_demos=4, max_labeled_demos=4, auto="light", num_candidates=None, num_threads=None, init_temperature=1.0, track_stats=True)
- Uses Bayesian-optimization search over the joint instruction and demo space.
- The prompt_model (often a stronger LLM) proposes candidate instructions, while the task_model (your student) executes the program and evaluate it.
- Searches for effective combinations of instructions and demonstrations rather than optimizing each independently.
MIPROv2 is a strong choice when both instructions and demonstrations need improvement.
dspy.SIMBA(metric, bsize=32, num_candidates=6, max_steps=8, max_demos=4, prompt_model=None, teacher_settings=None, num_threads=None)
- Mini-batch SGD-flavored search.
- Each optimization step samples a minibatch of examples, finds the worst-scoring examples, and uses prompt_model to propose improvements.
- Improvements can take the form of a natural-language rule (instruction patch) or a new demo.
dspy.InferRules(num_candidates=10, num_rules=10, num_threads=None, **bootstrap_kwargs)
- Built on top of BootstrapFewShot
- First bootstraps successful demonstrations from the training set.
- Then asks a teacher LLM to analyze these demonstrations and extract generalizable rules.
- Appends the extracted rules to the program instructions.
This is useful when successful examples contain recurring patterns that can be expressed as explicit instructions.
Next, let’s look at weight optimization.
dspy.BootstrapFinetune(metric=None, multitask=True, train_kwargs=None, adapter=None, exclude_demos=False, num_threads=None)
- Fine-tunes the underlying model weights using successful execution traces.
- First bootstraps successful traces in a similar way to BootstrapFewShot.
- Converts these traces into training data and uses them to fine-tune the student LLM.
Unlike prompt-only optimization, this approach requires a model and training infrastructure that support fine-tuning.
Finally, DSPy also provides several composite and specialized optimizers.
- dspy.BetterTogether: A meta-optimizer that combines different optimization approaches in sequence. For example, it can optimize prompts first, fine-tune model weights, and then optimize prompts again.
- dspy.Ensemble: Not an optimizer in the traditional sense, but a way to combine multiple candidate programs during inference. It uses a custom reduce_fn to aggregate their outputs.
- dspy.AvatarOptimizer: Designed for agent-style programs. It partitions the training set into positive and negative examples based on metric scores. An LLM then analyzes the differences between successful and unsuccessful examples and proposes instruction edits to improve the program.
DSPy optimizer selection guide
Now that we’ve covered the different optimizers, let’s summarize when to choose each one.
1. Just starting — unsure what helps
- Try:
BootstrapFewShot - Automatically generates demonstrations from successful model predictions.
2. Demonstrations vary in quality across attempts
- Try:
BootstrapFewShotWithRandomSearch - Explores different combinations of demonstrations to find better-performing sets.
3. Large training set — different inputs need different demonstrations
- Try:
KNNFewShot - Selects relevant demonstrations based on input similarity.
4. Instructions look wrong, but demonstrations look fine
- Try:
COPROorGEPA - Optimizes the instructions while retaining useful demonstrations.
5. Both instructions and demonstrations look weak, and you have compute budget
- Try:
MIPROv2orGEPA - Optimizes instructions and demonstrations together.
6. Failure cases share a recognizable pattern
- Try:
SIMBAorInferRules - Uses failure analysis to improve the prompt or derive better rules.
7. Prompt-only optimization has plateaued, but the model is tunable
- Try:
BootstrapFinetune - Uses successful demonstrations to help fine-tune model weights.
8. You want to combine prompt optimization and weight tuning
- Try:
BetterTogether - Combines prompt optimization with model fine-tuning.
9. You have multiple competent programs to combine
- Try:
Ensemble - Combines multiple programs to improve overall performance.
10. Agent or tool-use tasks
- Try:
AvatarOptimizerorGEPA - Optimizes agent behavior and tool-use performance.
Final thoughts
DSPy provides a broad range of optimizers, but there is no universally best choice. The right optimizer depends on what is limiting your program, the quality and quantity of your labeled data, and how much compute you can afford.
It is good to start with a simple baseline such as BootstrapFewShot, establish a reliable evaluation process, and then identify whether instructions, demonstrations, or model weights are the actual bottleneck.
More sophisticated optimization is not necessarily better. The goal is to find the simplest approach that delivers measurable improvements on representative, unseen data.

