Compare a component¶
A sweep file names the arms, and Lasagna runs each arm through the same pipeline with everything else held fixed. This page covers the sweep file, the stages you can sweep, and how to read the verdict.
Check the setup first¶
lasagna benchmark run smoke --profile speed
smoke is 24 documents and 12 queries held inside the package. There is no
dataset download and no API key, and on speed the only model is a 92 MB
embedder. A green run means the stack is wired up. The command measures
retrieval unless asked for more. Add --tracks retrieval,generation once an
LLM key and the eval extra are set up. The scores mean "this works", never
"this is good".
lasagna benchmark list names every benchmark and lasagna benchmark info
describes one. The benchmark catalog lists them
all.
Write the sweep¶
name: does my reranker help
benchmarks:
- name: beir/scifact
n_samples: 200
tracks: [retrieval] # NDCG, recall and the rest; no LLM, no judge
pipeline:
base_profile: speed # a 92 MB embedder and no API key
rerankers: [cross_encoder, "my_ideas:keyword_overlap"]
n_runs: 1
lasagna benchmark experiment sweep.yaml
Name base_profile in every sweep. Without it, each arm starts from standard
(a 3.2 GB download), while lasagna benchmark run starts from speed. The run
header and --dry-run print the profile each arm starts from.
The same sweep in Python:
import asyncio
from lasagna.knowledge_evaluation_sdk import Experiment, ExperimentConfig
from lasagna.knowledge_evaluation_sdk.experiment import BenchmarkSpec, PipelineSweepSpec
config = ExperimentConfig(
name="does my reranker help",
benchmarks=[BenchmarkSpec(name="beir/scifact", n_samples=200)],
tracks=["retrieval"],
pipeline=PipelineSweepSpec(
base_profile="speed",
rerankers=["cross_encoder", "my_ideas:keyword_overlap"],
),
n_runs=1,
)
results = asyncio.run(Experiment(config).run())
for task in results.failed_tasks:
print("failed:", task.task_key, task.error)
print(results.to_markdown("ndcg@10"))
Look at the failed tasks before the scores. A task that failed is listed there and not in the table.
Sweep any stage¶
Five stages have a list key. The others are named in a config_overrides entry.
| Stage | How to sweep it |
|---|---|
| reranker | rerankers: [...] |
| query enhancer | query_enhancers: [...] |
| chunker | chunkers: [...] |
| embedder | embedders: [...] |
| generator | generators: [...] |
| retriever | config_overrides: [{name: bm25, retriever: bm25}] |
| agentic pipeline | config_overrides: [{name: ircot, agentic_pipeline: ircot}] |
| document processor | does not sweep; check it on your own files |
An agentic pipeline arm changes generation metrics only, so sweep it with the
generation track. A chunker or embedder arm gets a corpus of its own, chunked or embedded by its
component, so arms never share an index. Every chunking or embedding setting
(chunk_size, chunk_unit, embedding_model and so on) gets its own corpus in
the same way.
A retriever¶
Put dense, the default vector search as a component, in as the control. Then
every arm names its first stage.
name: which first stage
benchmarks:
- name: beir/scifact
n_samples: 200
tracks: [retrieval]
pipeline:
base_profile: speed
config_overrides:
- {name: dense, retriever: dense} # the control
- {name: bm25, retriever: bm25}
- {name: hybrid_rrf, retriever: hybrid_rrf}
n_runs: 1
This one is keyless: BM25 needs no model, and speed embeds with the 92 MB one.
A generator¶
List the answer strategies under generators:. Every arm retrieves the same
way, so a difference comes from how the answer was written. concise is the
harness's own prompt, word for word, so it is the control.
name: how the answer is written
benchmarks:
- name: hotpotqa
n_samples: 100
tracks: [generation]
pipeline:
base_profile: speed
generators: [concise, cited, chain_of_note]
n_runs: 3 # repeat runs when an LLM stage is in the loop
This one needs an LLM API key and the eval extra (see
an LLM for the generation track).
cited answers carry [n] markers, and the citation metrics score them. From
the shell, lasagna benchmark run <name> --generator cited runs one generator.
An ensemble of rerankers¶
ensemble averages the scores of the cross-encoders an arm names in
ensemble_reranker_models. Weights go in ensemble_reranker_weights and are
equal when left out. An arm with fewer than two models, or with one that cannot
load, fails instead of scoring as one model.
pipeline:
base_profile: speed
config_overrides:
- {name: minilm, reranker: cross_encoder} # the control
- name: ensemble
reranker: ensemble
ensemble_reranker_models: [cross-encoder/ms-marco-MiniLM-L-6-v2, cross-encoder/ms-marco-TinyBERT-L-2-v2]
mmr and combined (the cross-encoder followed by MMR) run MMR whatever
enable_mmr says, because naming them is what turns it on.
Live mode and pooled corpora¶
A component override is only measured in live mode. generation_only never
constructs the pipeline, so passing reranker= there is a hard error rather than
a plausible number scored from gold context. The default mode: auto picks the
right mode and logs which it chose and why.
SQuAD v2, HotpotQA, MuSiQue and RAGBench ship each question's supporting
passages inline and no corpus to search. In auto mode Lasagna pools those
passages across every sample in the run, so each query has to find its own among
all the others'. That is a real retrieval task, but an easier one: the pool holds
no hard negatives, so recall reads higher than it would against a corpus of the
same size. These runs are stamped corpus_provenance: pooled_gold, which is part
of the comparability key, so a pooled number never shares a leaderboard row with
a BEIR one. For the same questions against a real corpus, use beir/hotpotqa.
Natural Questions and TriviaQA carry no context at all and stay
generation_only.
How many runs¶
Retrieval with fixed models is deterministic. The same queries return the same
ranked lists on every run, and the significance tests pair arms query by query,
so n_runs: 1 gives the same answer as three runs, three times faster. A sweep
that repeats identical runs says so when it ends.
Repeat runs when an LLM stage is in the loop: an answer generator, an LLM
reranker, HyDE or an agentic pipeline. A model that takes no temperature (see
temperature and repeatable runs)
varies from run to run whatever you ask for. There, n_runs: 3 or more measures
the spread, and variance_source: run keeps the queries fixed so the spread is
run to run rather than a resampled subset.
When corpus documents fail to ingest¶
A document that cannot be chunked (an error, or more than 120 s) is left out of the corpus, and the log names it. A partial corpus scores lower than the benchmark would, so every task on it fails, naming the count, the first documents and the setting that accepts it:
ingest_failure_tolerance: 0.02 # up to 2% of the corpus may be missing (at most 0.1)
A run that accepts a partial corpus records corpus_ingest_gap in each result.
That is part of the comparability key, so the run never shares a leaderboard
group with one on the complete corpus, and the bundle carries a PARTIAL_CORPUS
warning.
Read the verdict¶
lasagna results significance "<output>/results.json" -m ndcg@10 --all
The command prints each pair of arms with the difference, the effect size and
both the raw and the Holm-adjusted p-value. It ends in a box. Green names each
pair that survives the correction, which side won and by how much. Yellow says
nothing survived and points at lasagna results power, which tells "the arms
perform alike" apart from "too few queries to tell".
| Question | Command |
|---|---|
| Is the gap real, across every pair of arms? | lasagna results significance results.json --all |
| Could this run have detected the effect I care about? | lasagna results power results.json baseline candidate |
| Do two techniques compose, or overlap? | lasagna results interaction results.json (a 2x2 run) |
| Which queries did an arm lose? | lasagna results per-query results.json --sort-by ndcg@10 |
| Are two runs comparable at all? | lasagna results compare a.json b.json |
Significance testing explains what the test does. Results, bundles and leaderboards covers sharing the run.
Three more checks¶
Dry run and cost estimates tells you what a sweep costs before it starts. Baselines adds the arms a reviewer asks for. The LLM response cache makes a second run of a sweep spend nothing on what it already asked.