Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 89ac998

Browse files
authored
Add LoCoMo and BEAM benchmark harnesses (#204)
* Add LoCoMo and BEAM benchmark harnesses * Address benchmark review feedback * Fix benchmark ingest pairing * Document benchmark additions in changelog * Fix BEAM sampling and evaluation
1 parent 11ea976 commit 89ac998

20 files changed

Lines changed: 1968 additions & 2 deletions

‎.gitignore‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ benchmarks/longmemeval/**/*.pyc
6666
benchmarks/longmemeval/data/
6767
benchmarks/longmemeval/results/
6868
benchmarks/longmemeval/outputs/
69+
!benchmarks/common/
70+
!benchmarks/common/**
71+
benchmarks/common/**/__pycache__/
72+
benchmarks/common/**/*.pyc
73+
!benchmarks/locomo/
74+
!benchmarks/locomo/**
75+
benchmarks/locomo/**/__pycache__/
76+
benchmarks/locomo/**/*.pyc
77+
benchmarks/locomo/data/
78+
benchmarks/locomo/results/
79+
benchmarks/locomo/outputs/
80+
!benchmarks/beam/
81+
!benchmarks/beam/**
82+
benchmarks/beam/**/__pycache__/
83+
benchmarks/beam/**/*.pyc
84+
benchmarks/beam/data/
85+
benchmarks/beam/results/
86+
benchmarks/beam/outputs/
6987
backboard/
7088
rust/
7189

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
## Unreleased
44

5+
- Add modular LoCoMo and BEAM benchmark runners for the Python XMem API.
56
- Add local XMem setup through `npx create-xmem@latest` and `npm run dev`.
67
- Add local Docker storage, Chrome extension build patching, diagnostics, verification, and context export/import/sync commands.

‎benchmarks/README.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
This directory contains benchmark harnesses for XMem.
44

55
- `longmemeval/`: Python-only LongMemEval benchmark runner targeting the XMem HTTP API.
6+
- `locomo/`: Python-only LoCoMo benchmark runner targeting the XMem HTTP API.
7+
- `beam/`: Python-only BEAM runner, defaulting to the Hugging Face BEAM 1M split.
68

79
Benchmark runs can create large dataset and result artifacts. Keep those files under
8-
`benchmarks/longmemeval/data`, `benchmarks/longmemeval/results`, or
9-
`benchmarks/longmemeval/outputs`; those paths are intentionally ignored by git.
10+
each benchmark's `data`, `results`, or `outputs` directory; those paths are
11+
intentionally ignored by git.

‎benchmarks/beam/README.md‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# BEAM 1M Benchmark for XMem Python
2+
3+
This harness benchmarks the Python XMem API on the BEAM dataset, defaulting to
4+
the `1M` split from `Mohammadta/BEAM` on Hugging Face. It does not run or
5+
compare the Go implementation.
6+
7+
BEAM rows contain long `chat` histories and stringified `probing_questions`.
8+
The dataset card lists ten memory ability types: abstention, contradiction
9+
resolution, event ordering, information extraction, instruction following,
10+
knowledge update, multi-session reasoning, preference following, summarization,
11+
and temporal reasoning.
12+
13+
## Dependencies
14+
15+
BEAM is distributed as parquet. Install `pyarrow` before reading the downloaded
16+
dataset:
17+
18+
```bash
19+
pip install pyarrow
20+
```
21+
22+
## Smoke Check
23+
24+
```bash
25+
python -m benchmarks.beam.run \
26+
--split 1M \
27+
--download \
28+
--dry-run \
29+
--limit 1
30+
```
31+
32+
This downloads the BEAM 1M parquet file, validates parsing, counts ingest items,
33+
and does not call XMem.
34+
35+
## Run Against XMem
36+
37+
```bash
38+
export XMEM_API_KEY="..."
39+
40+
python -m benchmarks.beam.run \
41+
--split 1M \
42+
--dataset-path benchmarks/beam/data/1M-00000-of-00001.parquet \
43+
--api-base-url https://api.xmem.in \
44+
--output-dir benchmarks/beam/results/beam-1m
45+
```
46+
47+
To run a balanced slice, sample an equal percentage from each BEAM question
48+
type:
49+
50+
```bash
51+
python -m benchmarks.beam.run \
52+
--split 1M \
53+
--dataset-path benchmarks/beam/data/1M-00000-of-00001.parquet \
54+
--sample-percent-per-question-type 1 \
55+
--api-base-url https://api.xmem.in \
56+
--output-dir benchmarks/beam/results/beam-1m-1pct
57+
```
58+
59+
Outputs:
60+
61+
- `results.jsonl`: full per-question records with local proxy metrics
62+
- `predictions.jsonl`: `question_id` and `hypothesis`
63+
- `summary.json`: local exact/contains/token-F1 grouped by BEAM question type
64+
65+
## Judge Evaluation
66+
67+
The benchmark runner writes the BEAM rubric for each question into
68+
`results.jsonl`. To compute BEAM-style pass rate and average judge score with an
69+
OpenAI judge model:
70+
71+
```bash
72+
export OPENAI_API_KEY="..."
73+
74+
python -m benchmarks.beam.evaluate \
75+
--results-path benchmarks/beam/results/beam-1m-1pct/results.jsonl \
76+
--output-dir benchmarks/beam/results/beam-1m-1pct
77+
```
78+
79+
This writes:
80+
81+
- `evaluations.jsonl`: per-question rubric judge scores and reasons
82+
- `evaluation_summary.json`: pass rate and average judge score overall and by
83+
question type
84+
85+
The pass threshold is `judge_score >= 0.5`, matching the usual BEAM pass-rate
86+
interpretation over rubric scores.

‎benchmarks/beam/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""BEAM benchmark harness for the Python XMem API."""

‎benchmarks/beam/config.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Configuration for the BEAM benchmark harness."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
from dataclasses import dataclass
7+
from pathlib import Path
8+
9+
10+
DEFAULT_API_BASE_URL = "https://api.xmem.in"
11+
DEFAULT_API_KEY_ENV = "XMEM_API_KEY"
12+
DEFAULT_SPLIT = "1M"
13+
DEFAULT_DATASET_URLS = {
14+
"100K": (
15+
"https://huggingface.co/datasets/Mohammadta/BEAM/resolve/main/"
16+
"data/100K-00000-of-00001.parquet"
17+
),
18+
"500K": (
19+
"https://huggingface.co/datasets/Mohammadta/BEAM/resolve/main/"
20+
"data/500K-00000-of-00001.parquet"
21+
),
22+
"1M": (
23+
"https://huggingface.co/datasets/Mohammadta/BEAM/resolve/main/"
24+
"data/1M-00000-of-00001.parquet"
25+
),
26+
}
27+
28+
29+
@dataclass(frozen=True)
30+
class BenchmarkConfig:
31+
dataset_path: Path
32+
output_dir: Path
33+
api_base_url: str = DEFAULT_API_BASE_URL
34+
api_key_env: str = DEFAULT_API_KEY_ENV
35+
api_timeout_seconds: float = 120.0
36+
max_retries: int = 3
37+
retry_backoff_seconds: float = 2.0
38+
batch_size: int = 25
39+
ingest_api_version: str = "v2"
40+
poll_interval_seconds: float = 2.0
41+
poll_timeout_seconds: float = 1800.0
42+
top_k: int = 10
43+
effort_level: str = "low"
44+
user_prefix: str = "beam"
45+
limit: int | None = None
46+
offset: int = 0
47+
question_type: str | None = None
48+
sample_percent_per_question_type: float | None = None
49+
sample_min_per_question_type: int = 1
50+
sample_seed: int = 13
51+
split: str = DEFAULT_SPLIT
52+
skip_ingest: bool = False
53+
resume: bool = True
54+
dry_run: bool = False
55+
56+
@property
57+
def api_key(self) -> str:
58+
return os.getenv(self.api_key_env, "").strip()
59+
60+
def require_api_key(self) -> str:
61+
api_key = self.api_key
62+
if not api_key:
63+
raise RuntimeError(
64+
f"Missing API key. Set {self.api_key_env} before running BEAM."
65+
)
66+
return api_key

0 commit comments

Comments
 (0)