test(core): benchmark multilingual embeddings · basicmachines-co/basic-memory@1882190 · GitHub
Skip to content

Commit 1882190

Browse files
committed
test(core): benchmark multilingual embeddings
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 8bde8f5 commit 1882190

10 files changed

Lines changed: 1535 additions & 13 deletions
Lines changed: 152 additions & 0 deletions

docs/semantic-search.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,31 @@ Basic Memory passes `semantic_embedding_model` to FastEmbed, so you can select a
150150
embedding model registered by the installed FastEmbed version. Basic Memory does not maintain a
151151
second model allowlist or add arbitrary Hugging Face models to FastEmbed.
152152

153+
The default remains `BAAI/bge-small-en-v1.5`. It is the smaller, lower-memory choice and avoids an
154+
automatic embedding rebuild for existing installations. Projects that need semantic retrieval
155+
across multiple languages can opt into multilingual MiniLM:
156+
157+
```bash
158+
basic-memory config set semantic_embedding_provider fastembed
159+
basic-memory config set semantic_embedding_model \
160+
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
161+
basic-memory config set semantic_embedding_dimensions 384
162+
bm reindex --embeddings
163+
```
164+
165+
Multilingual MiniLM covers many languages and keeps the default model's 384 dimensions.
166+
The matching dimensions avoid a vector-schema dimension change, but the two models' vectors are
167+
not interchangeable: switching in either direction still requires `bm reindex --embeddings`.
168+
169+
Basic Memory's checked-in multilingual evaluation found that MiniLM preserved the English
170+
baseline while improving same-language, cross-language, and mixed-language ranking across
171+
Chinese, Japanese, Korean, Arabic, Russian, Spanish, and Thai. It also has a larger local artifact
172+
and materially higher memory use than the default. The current `0.55` similarity cutoff hid some
173+
correctly ranked MiniLM results in the small judged corpus, so deployments should measure their
174+
own queries before changing `semantic_min_similarity`. See the
175+
[multilingual embedding benchmark](multilingual-embedding-benchmark.md) for the corpus, results,
176+
limitations, and Cloud follow-up.
177+
153178
List the models available in the same Python environment as Basic Memory. For a `uv tool`
154179
installation on macOS or Linux:
155180

@@ -216,8 +241,8 @@ projects receive the same Postgres analysis through the managed full fleet reind
216241
do not run a local command.
217242

218243
This is lexical matching, independent of the configured embedding model. Vector and hybrid search
219-
quality in these languages still depends on choosing a multilingual embedding model such as the
220-
Jina Chinese-English model or multilingual E5 configured above.
244+
quality in these languages still depends on choosing a multilingual embedding model such as
245+
multilingual MiniLM or multilingual E5 configured above.
221246

222247
### OpenAI
223248

justfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,30 @@ test-litellm-live *args:
396396
test-semantic-postgres:
397397
BASIC_MEMORY_ENV=test uv run pytest -p pytest_mock -v --no-cov {{PYTEST_FLAGS}} -m semantic -k postgres test-int/semantic/
398398

399+
# Run one multilingual model/backend pair in its own process so cold-load and RSS
400+
# measurements are not contaminated by another FastEmbed model.
401+
benchmark-multilingual model="bge-small-en" backend="sqlite" mode="vector" threshold="0.55":
402+
UV_PYTHON=3.12 \
403+
BASIC_MEMORY_ENV=test \
404+
LOGFIRE_IGNORE_NO_CONFIG=1 \
405+
BASIC_MEMORY_MULTILINGUAL_MODEL="{{model}}" \
406+
BASIC_MEMORY_MULTILINGUAL_BACKEND="{{backend}}" \
407+
BASIC_MEMORY_MULTILINGUAL_RETRIEVAL_MODE="{{mode}}" \
408+
BASIC_MEMORY_MULTILINGUAL_THRESHOLD="{{threshold}}" \
409+
BASIC_MEMORY_BENCHMARK_OUTPUT=".benchmarks/multilingual-{{model}}-{{backend}}-{{mode}}-{{threshold}}.jsonl" \
410+
uv run --extra milvus pytest -p pytest_mock -q --no-cov {{PYTEST_FLAGS}} \
411+
test-int/semantic/test_multilingual_embedding_benchmark.py
412+
413+
# Screen the current model and the 384-dimensional multilingual candidate, then
414+
# compare the latest records from their JSONL artifacts.
415+
benchmark-multilingual-compare backend="sqlite" mode="vector" threshold="0.55":
416+
just benchmark-multilingual bge-small-en "{{backend}}" "{{mode}}" "{{threshold}}"
417+
just benchmark-multilingual multilingual-minilm "{{backend}}" "{{mode}}" "{{threshold}}"
418+
UV_PYTHON=3.12 uv run python test-int/compare_search_benchmarks.py \
419+
".benchmarks/multilingual-bge-small-en-{{backend}}-{{mode}}-{{threshold}}.jsonl" \
420+
".benchmarks/multilingual-multilingual-minilm-{{backend}}-{{mode}}-{{threshold}}.jsonl" \
421+
--format table
422+
399423
# View semantic benchmark results (rich formatted table)
400424
# Usage: just semantic-report [--filter-combo sqlite] [--filter-suite paraphrase] [--sort-by avg_latency_ms]
401425
semantic-report *args:

test-int/BENCHMARKS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ pytest test-int/test_search_performance_benchmark.py::test_benchmark_search_incr
3636
pytest test-int/test_search_performance_benchmark.py -v -m benchmark
3737
```
3838

39+
### Multilingual embedding model comparison
40+
41+
The multilingual harness runs one FastEmbed model per process so model-load and RSS measurements
42+
remain isolated. It supports SQLite/sqlite-vec, PostgreSQL/pgvector, and PostgreSQL/Milvus. The
43+
Milvus case uses the production adapter with an isolated Milvus Lite database:
44+
45+
```bash
46+
just benchmark-multilingual bge-small-en sqlite vector 0.55
47+
just benchmark-multilingual multilingual-minilm postgres vector 0.55
48+
just benchmark-multilingual multilingual-minilm milvus vector 0.55
49+
just benchmark-multilingual-compare sqlite vector 0.55
50+
just benchmark-multilingual-compare milvus vector 0.55
51+
```
52+
53+
See [Multilingual Embedding Benchmark](../docs/multilingual-embedding-benchmark.md) for the corpus,
54+
model keys, metrics, initial results, and Cloud handoff.
55+
3956
### Write JSON benchmark artifacts
4057
```bash
4158
BASIC_MEMORY_BENCHMARK_OUTPUT=.benchmarks/search-benchmarks.jsonl \

test-int/compare_search_benchmarks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
HIGHER_IS_BETTER_SUFFIXES = ("_per_sec",)
1515
HIGHER_IS_BETTER_PREFIXES = ("hit_rate_", "recall_", "mrr_")
1616
EQUAL_IS_BETTER_KEYS = {"notes_indexed", "queries_executed"}
17+
LOWER_IS_BETTER_KEYS = {
18+
"accepted_empty_rate",
19+
"negative_false_positive_rate",
20+
"wrong_top_rate",
21+
"model_cache_bytes",
22+
"peak_rss_bytes",
23+
"rss_after_index_bytes",
24+
"rss_after_load_bytes",
25+
"rss_model_delta_bytes",
26+
"vector_storage_bytes",
27+
}
1728

1829

1930
@dataclass(frozen=True)
@@ -27,6 +38,8 @@ def _preference_for_metric(metric_name: str) -> str:
2738
"""Return optimization preference for a metric."""
2839
if metric_name in EQUAL_IS_BETTER_KEYS:
2940
return "equal"
41+
if metric_name in LOWER_IS_BETTER_KEYS:
42+
return "lower"
3043
if metric_name.startswith(HIGHER_IS_BETTER_PREFIXES):
3144
return "higher"
3245
if metric_name.endswith(HIGHER_IS_BETTER_SUFFIXES):

test-int/semantic/conftest.py

Lines changed: 30 additions & 11 deletions

0 commit comments

Comments
 (0)