bench quality result files#
This page describes every file and field that lmcache bench quality writes to a results directory: what each file is for, which stage writes it and when, and the type and meaning of each field. It is meant for anyone -- a person, a script, or an AI agent -- reading results without the source at hand.
All files are JSON (.json, one object) or JSON Lines (.jsonl, one
object per line), UTF-8, and strict JSON: no Infinity or NaN, so any
JSON parser reads them.
<results dir>/
plan.json the plan: settings, prompts, questions, golds
manifest.jsonl the reuse manifest, one record per question
arm_<label>.json one arm pass: metadata, written last
answers_<label>.jsonl that pass's answers, one record per question
judge_cache.jsonl judge ratings, reused by later analyzes
rows.jsonl per-question comparison, written by analyze
summary.json the verdict, written by analyze
Lifecycle of the files#
File |
Written by |
When |
Replaced or deleted |
|---|---|---|---|
|
|
The first |
Never rewritten; |
|
|
As each pass finishes: answers first, the arm file last. |
The first new pass of a role deletes that role's earlier files (and
|
|
|
Every |
Overwritten by the next |
|
|
Appended as each new judgment finishes. |
Never deleted. Delete it yourself after changing what serves the judge model name. |
Every file except the append-only judge cache is written atomically: to
<file>.tmp first, then renamed into place, so a reader never sees half a
file. A .tmp left behind by a killed process is never read. An
answers_<label>.jsonl without its arm_<label>.json is from a pass
that did not finish; analyze ignores it with a warning.
Conventions used by every file#
Labels. Each arm pass has a label, used as the file-name suffix and as
the key in per-arm objects: reference_1, reference_2, … for the
reference passes (--reference-repeats), control and candidate.
Request ids. Store requests are store0, store1, …; measured
questions are q0, q1, …, in plan order.
Digests. All are lowercase hex prefixes of SHA-256:
Digest |
Length |
Input |
|---|---|---|
|
16 |
The question's measured messages ( |
|
12 |
The raw passage text, before chunk padding. Equal text, equal id. |
|
16 |
Every store and measured message, in order. |
|
16 |
The plan settings (see plan.json) and |
|
16 |
The plan settings, the verdict metric, the judge model (with the
judge metric) and |
judge cache |
64 |
The judge metric id, judge model, question, answer and reference. |
Times are ISO 8601, to the second, in local time with the UTC offset
(2026-10-19T10:41:12-05:00).
Versions. lmcache_version is <version>-<commit> (or just the
version, or "unknown" in a source checkout). engine_version is what
the engine reported at GET /version, or null.
Schema version. plan.json and every arm_<label>.json carry
schema_version (currently 1). run and analyze refuse a
directory written with another version (exit 2) rather than misread it. The
other files have no version of their own: they belong to the plan in the
same directory.
Special values. Where a value can be missing, the files use null or
a sentinel:
Field |
Meaning of the special value |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Not measured: |
Conditional keys. rows.jsonl's match and summary.json's
settings.judge_model appear only with the judge metric, and
summary.json's judge is null without it. loss_tags holds only
the tags that occur; evidence_sources always has all three keys.
Counters are floats, because they are read from Prometheus, even when they count tokens or chunks.
plan.json#
The plan every arm receives, saved so that analyze needs no tokenizer
or dataset and so that later runs can check they build the same prompts.
Field |
Type |
Description |
|---|---|---|
|
int |
|
|
string |
16 hex. Every |
|
string |
16 hex, over every store and measured message. |
|
string |
The lmcache that wrote the plan (the first |
|
string |
When the first |
|
object |
The run settings that shape the prompts (below). Every |
|
object |
The plan itself (below). |
settings:
Field |
Type |
Description |
|---|---|---|
|
string |
|
|
string |
|
|
string |
|
|
string |
|
|
int |
|
|
int |
|
|
int |
|
|
object |
|
|
int |
|
|
string |
|
|
int |
|
The arm URLs, --reference-repeats and --capture-blend-metrics are not
settings: runs of different arms into one directory may differ in them.
plan:
Field |
Type |
Description |
|---|---|---|
|
string, int |
Same as in |
|
array |
In send order: |
|
array |
One per question, in dataset order: the question's
manifest.jsonl record plus |
|
object |
|
{
"schema_version": 1,
"plan_fingerprint": "d7428c9bc57348c7",
"prompts_digest": "ca2bc4ffc3dc39b2",
"lmcache_version": "0.4.0-1a2b3c4",
"created_at": "2026-10-19T10:30:05-05:00",
"settings": {"model": "Qwen/Qwen3-8B", "dataset": "hotpotqa", "task": "qa",
"scenario": "independent", "num_samples": 200, "chunk_size": 256,
"max_output_tokens": 2048, "template_kwargs": {}, "seed": 42},
"plan": {"scenario": "independent", "chunk_size": 256,
"store_requests": [{"request_id": "store0", "messages": ["..."]}],
"samples": ["..."]}
}
manifest.jsonl#
The reuse manifest: for every question, how each of its passages got into
the cache. One record per question, in plan order. analyze does not read
it (plan.json holds the same records); it is there to read and grep.
Field |
Type |
Description |
|---|---|---|
|
string |
The dataset's question id. |
|
string, int |
The plan's, repeated on every line. |
|
string |
|
|
string |
16 hex. Equal on every arm: the prompts are byte-identical. |
|
string |
How the answer-bearing passages were found: |
|
array |
|
|
int or null |
Tokens of the rendered request covered by a whole chunk of a
rendered store request, at any offset: what the blend server's
matcher can reuse. |
|
int or null |
|
|
string or null |
|
|
array |
One per passage, in measured order (below). |
passages[]:
Field |
Type |
Description |
|---|---|---|
|
string |
12 hex. |
|
string |
|
|
string |
|
|
int |
Its index among that store request's passages; always |
|
int |
Its index in the measured prompt. |
|
bool |
Whether it holds the answer (see |
|
array |
|
|
int |
Padding tokens added to align it to whole chunks (the filler only,
not the |
{"qid": "s1", "scenario": "independent", "chunk_size": 256, "request_id": "q1",
"prompt_hash": "3ef3226f2db0ad8c", "evidence_source": "answer_string",
"evidence_passage_ids": ["193996aa3260"],
"passages": [
{"passage_id": "2408066aee02", "origin": "alone", "store_request_id": "store1",
"stored_position": 0, "measured_position": 0, "is_evidence": false,
"measured_earlier_in": ["q0"], "pad_tokens": 118},
{"passage_id": "193996aa3260", "origin": "alone", "store_request_id": "store2",
"stored_position": 0, "measured_position": 1, "is_evidence": true,
"measured_earlier_in": [], "pad_tokens": 97}]}
arm_<label>.json#
One per arm pass: where and how the pass ran. Written after the pass's answers, so its presence marks the pass complete.
Field |
Type |
Description |
|---|---|---|
|
int |
|
|
string |
Must equal |
|
string |
|
|
int |
0-based repeat; above 0 only for reference passes. |
|
string |
|
|
string |
The engine URL, as given to |
|
string |
|
|
string or null |
The engine's reported version. |
|
string |
The lmcache that ran the pass. |
|
string |
When the pass started and finished. |
|
bool |
Whether blend-server counters were read. |
|
object or null |
The pass's blend-server counter deltas, read with a short settle
delay at each end (below); |
|
int |
Store requests that failed; always |
|
int |
Questions answered in the pass. |
counters (also per question in answers_<label>.jsonl):
Field |
Description |
|---|---|
|
Chunk-aligned prompt tokens the blend server was asked to look up. |
|
Of those, served as an exact prefix. |
|
Of those, served by blending reused chunks at new positions. Blend
share = |
|
Prefix chunks loaded past an L2 load failure, at their original
positions ( |
|
Chunks whose fingerprint matched but whose KV was gone. |
|
Chunks added to the blend server's fingerprint index during the pass. |
{"schema_version": 1, "plan_fingerprint": "d7428c9bc57348c7", "role": "candidate",
"run_index": 0, "label": "candidate", "url": "http://gpu1:8000",
"blend_metrics_url": "http://gpu1:9090/metrics", "engine_version": "0.11.0",
"lmcache_version": "0.4.0-1a2b3c4",
"started_at": "2026-10-19T10:52:40-05:00", "finished_at": "2026-10-19T11:03:17-05:00",
"engagement_measured": true,
"counters": {"requested_tokens": 1024.0, "prefix_hit_tokens": 256.0,
"non_prefix_hit_tokens": 768.0, "segmented_prefix_hit_tokens": 0.0,
"stale_chunks": 0.0, "fingerprints_registered": 8.0},
"store_failures": 0, "num_questions": 200}
answers_<label>.jsonl#
The raw outcome of every measured question in one pass, in plan order. This
is where to look when an arm's answer is surprising: response holds the
full text, reasoning included.
Field |
Type |
Description |
|---|---|---|
|
string |
The dataset's question id. |
|
string |
|
|
bool |
Whether the request produced output. |
|
string |
The text inside the last |
|
string |
The full response, reasoning included. |
|
number |
Time to first token in seconds, rounded to 4 places; |
|
int |
Tokens generated. |
|
object or null |
This question's counter deltas (fields as in arm_<label>.json). Approximate: a lookup the server records after the response lands in the next question's deltas. |
{"qid": "s2", "request_id": "q2", "successful": true, "answer": "Geneva",
"response": "Bern is the federal city, but ... <answer>Geneva</answer>",
"ttft": 0.0841, "num_output_tokens": 212,
"counters": {"requested_tokens": 256.0, "prefix_hit_tokens": 64.0,
"non_prefix_hit_tokens": 192.0, "segmented_prefix_hit_tokens": 0.0,
"stale_chunks": 0.0, "fingerprints_registered": 2.0}}
rows.jsonl#
The per-question comparison, one row per question in plan order -- including questions some arm could not be scored on. Start here to see which questions the candidate lost and why.
Field |
Type |
Description |
|---|---|---|
|
string |
The dataset's question id. |
|
array |
The gold answers ( |
|
object |
|
|
object |
Judge metric only: |
|
object |
|
|
string or null |
How the candidate's answer compares with |
|
bool or null |
Whether |
|
number or null |
This question's blend share (0 to 1). |
|
object |
|
|
array |
The evidence passages, in measured order:
|
{"qid": "s2", "gold": ["Bern"],
"scores": {"reference_1": 1.0, "reference_2": 1.0, "candidate": 0.0},
"match": {"reference_1": 1.0, "reference_2": 1.0, "candidate": 0.0},
"answers": {"reference_1": "Bern", "reference_2": "Bern", "candidate": "Geneva"},
"loss_tag": "wording", "reference_flipped": false, "blend_share": 0.75,
"evidence": [{"passage_id": "b887df1256ff", "origin": "alone",
"stored_position": 0, "measured_position": 0}]}
summary.json#
The verdict and everything needed to interpret it.
Field |
Type |
Description |
|---|---|---|
|
string |
16 hex. Two summaries are comparable only when it matches. |
|
string |
|
|
object |
|
|
object |
|
|
int |
Questions in the plan. |
|
int |
Questions left out of every comparison because some pass has no score for them (judge only). More than 5% makes the verdict INVALID. |
|
object or null |
|
|
object |
Question counts by |
|
number |
Share of measured passages that an earlier question also held
(see |
|
object or null |
|
|
object or null |
|
|
int |
Store requests in the plan. |
|
string |
|
|
array |
Why the verdict is INVALID; |
|
int |
For UNDERPOWERED, about how many questions would settle it; |
|
number |
Share of questions whose score differs between |
|
object |
The candidate pass against |
|
array |
Every other pass ( |
|
object |
Whether a blend-server candidate actually blended (below). |
|
object |
|
|
object |
|
|
object |
Counts of |
candidate and others[]:
Field |
Type |
Description |
|---|---|---|
|
string |
The pass. |
|
number |
Mean score over the questions every pass was scored on. |
|
number |
|
|
number or null |
Standard error of the paired per-question differences. The verdict
is FAIL only when |
engagement:
Field |
Type |
Description |
|---|---|---|
|
bool |
Whether the candidate was run with |
|
number |
Share of looked-up tokens served by blending, over the pass. |
|
number |
Lowest share over consecutive windows of 10 questions (equal to
|
|
number |
The pass's counters (see arm_<label>.json). |
|
bool |
Whether the gate's run-wide and minimum-window values reach its
minimum ( |
|
string |
What |
|
number or null |
Reuse gate only: observed reused tokens (prefix, segmented prefix and
blended) over the plan's expected reuse, over the pass. |
|
number or null |
Reuse gate only: lowest |
{
"run_fingerprint": "ec1601a6e6537308",
"plan_fingerprint": "d7428c9bc57348c7",
"settings": {"model": "Qwen/Qwen3-8B", "dataset": "hotpotqa", "...": "...",
"verdict_metric": "judge", "judge_model": "openai/gpt-oss-120b",
"reference_repeats": 2, "tolerance": 0.05, "min_blend_share": 0.3,
"min_reuse_ratio": 0.8, "blend_metrics_url": "http://gpu1:9090/metrics"},
"arms": {"candidate": {"url": "http://gpu1:8000", "engine_version": "0.11.0",
"lmcache_version": "0.4.0-1a2b3c4",
"started_at": "2026-10-19T10:52:40-05:00",
"finished_at": "2026-10-19T11:03:17-05:00"}, "...": "..."},
"num_questions": 200, "unscored_questions": 0,
"judge": {"model": "openai/gpt-oss-120b", "metric": "ragas-0.4.3/nv_accuracy"},
"evidence_sources": {"dataset": 200, "answer_string": 0, "none": 0},
"measured_earlier_share": 0.02, "num_store_requests": 1964,
"verdict": "FAIL", "invalid_reasons": [], "samples_needed": 0,
"reference_flip_rate": 0.03,
"candidate": {"label": "candidate", "mean_score": 0.768, "delta": -0.137,
"standard_error": 0.032},
"others": [{"label": "reference_2", "mean_score": 0.9, "delta": -0.005,
"standard_error": 0.0095}],
"engagement": {"measured": true, "blend_share": 0.912, "min_window_share": 0.874,
"stale_chunks": 0.0, "segmented_prefix_hit_tokens": 0.0,
"fingerprints_registered": 4180.0, "engaged": true,
"gate": "blend_share", "reuse_ratio": null,
"min_window_reuse_ratio": null},
"arm_counts": {"candidate": {"answered": 200, "empty": 0, "abstained": 9}, "...": "..."},
"store_failures": {"reference_1": 0, "reference_2": 0, "candidate": 0},
"loss_tags": {"none": 168, "candidate_higher": 4, "abstention": 9, "wording": 19}
}
judge_cache.jsonl#
Judge ratings, one line per judged (question, answer, gold answer) triple,
so rerunning analyze judges only what is new.
Field |
Type |
Description |
|---|---|---|
|
string |
64 hex, over the judge metric id, judge model, question, answer and the question's first gold answer. The judge URL is not part of it. |
|
number |
The mean of the available ratings, 0 to 1 in steps of 0.25. |
|
array |
|
Answers the judge could not rate at all are not cached, so a rerun tries
them again; neither are the sanity check and missing answers (those score
0.0 without a judge call). An unreadable line, such as a partial last
line from a killed run, is skipped and re-judged.
{"key": "e1b355a1cfb8035bf28b99ddae617b19739578a4302f584594ac82953c8d6a7f",
"score": 0.75, "ratings": [1.0, 0.5]}
Working with the results#
The questions the candidate lost, with both answers:
jq -c 'select(.loss_tag != null and .loss_tag != "none" and .loss_tag != "candidate_higher")
| {qid, gold, loss_tag, reference: .answers.reference_1, candidate: .answers.candidate}' \
out/rows.jsonl
reference_1's mean score, which summary.json leaves implicit:
jq '.candidate.mean_score - .candidate.delta' out/summary.json
Whether a verdict can be trusted before reading its numbers:
jq '{verdict, invalid_reasons, engaged: .engagement.engaged,
flip_rate: .reference_flip_rate, unscored: .unscored_questions}' out/summary.json
Whether two summaries can be compared (same prompts, metric and judge):
[ "$(jq -r .run_fingerprint a/summary.json)" = "$(jq -r .run_fingerprint b/summary.json)" ]
A lost question's evidence passages, and how each was cached:
jq -c 'select(.qid == "s2") | .passages[] | select(.is_evidence)' out/manifest.jsonl