Skip to content

Gate releases on embedding equivalence with llama.cpp - #107

Merged
bsbodden merged 5 commits into
mainfrom
feat/embedding-equivalence-gate
Aug 6, 2026
Merged

Gate releases on embedding equivalence with llama.cpp#107
bsbodden merged 5 commits into
mainfrom
feat/embedding-equivalence-gate

Conversation

@bsbodden

@bsbodden bsbodden commented Aug 6, 2026

Copy link
Copy Markdown
Member

Certifies that this runtime reproduces an embedding model, rather than grading the model.

Retrieval quality is a published property of the weights — Qwen3-Embedding-0.6B scores what it scores whoever runs it. What a runtime can get wrong is reproducing the model: pooling, rotary embeddings, dequantization, normalization. That has an unambiguous correct answer, so it is what this gates on. It also sidesteps inventing a retrieval threshold, which would be dataset-dependent and arbitrary.

Shape

./gradlew :models-bench:run --args="embedding-equivalence --model <artifact.gguf> [--report out.json]"

Eight probes chosen to exercise the paths a runtime gets wrong — single token, long input, non-Latin script, code, rare tokens, ordinary prose. No corpus, no retrieval metric, no scoring. Reference vectors are committed rather than recomputed, so only this side runs: 5.6s end to end, and no machine running the gate needs a built llama.cpp to re-derive a constant.

Exits 0 reproduced, 1 not reproduced, 2 usage or integrity problem.

Two floors, because cosine alone is not enough

Run Min cosine Max ‖v‖ − 1
Correct (this runtime vs the reference) 0.99950 2.7e-09
Mean pooling instead of last-token 0.66156
L2 normalization skipped 1.00000 ~11
floor 0.999 1e-3

Wrong pooling lands nowhere near the floor, so there is no threshold-tuning judgment call to get wrong.

But cosine is scale-invariant: a runtime that skips L2 normalization agrees with a normalized reference at exactly 1.0. That number is measured via llama-embedding --embd-normalize -1, not assumed. Callers that use a bare dot product as a cosine shortcut — as vectors does — would be silently wrong while the gate reported perfection. Hence the separate unit-length check.

The gate takes the worst probe, not the mean, because averaging lets one broken case hide behind seven good ones.

Integrity

The CLI refuses to run when the probe-set digest or the artifact digest has moved, so a stale reference fails loudly rather than comparing against vectors from different inputs. Verified: pointing it at a different model exits 2 with both digests printed.

Notes

  • MINIMUM_COSINE mirrors ModelEmbeddingQualification.MINIMUM_ORACLE_COSINE in ModelJars. Duplicated rather than imported because ModelJars depends on this project, not the reverse; every report writes the value it used, so the two cannot silently diverge.
  • Latency is recorded as diagnostics only. Eight probes cannot establish a throughput floor and this gate does not claim to.
  • Regeneration path documented in embedding-equivalence/README.md, including why each llama.cpp flag matters.

Unblocks qualifying and publishing the Qwen3-Embedding-0.6B ModelJar.

🤖 Generated with Claude Code

Certifies that this runtime reproduces an embedding model rather than
grading the model. Retrieval quality is a published property of the
weights; what a runtime can get wrong is pooling, rotary embeddings,
dequantization and normalization. Agreement with an independent
reference has an unambiguous correct answer, so that is what gates.

Eight probes exercise the paths a runtime gets wrong — single token,
long input, non-Latin script, code, rare tokens — and the reference
vectors are committed rather than recomputed, so the gate needs only
this side to run. 5.6s end to end.

Two floors, because cosine alone is not enough. Measured:

  correct runtime vs reference     min cosine 0.99950   |v|-1  2.7e-09
  mean pooling instead of last     min cosine 0.66156
  L2 normalization skipped         min cosine 1.00000   |v|-1  ~11
  floors                                      0.999            1e-3

Wrong pooling lands nowhere near the floor, so there is no threshold
judgment call to get wrong. But cosine is scale-invariant, so an
unnormalized runtime agrees with a normalized reference at exactly
1.0 — measured via llama-embedding --embd-normalize -1, not assumed.
Callers using a bare dot product as a cosine shortcut would be
silently wrong while the gate reported perfection, hence the separate
unit-length check.

The gate takes the worst probe, not the mean: averaging lets one
broken case hide behind seven good ones. It refuses to run when
either the probe-set or artifact digest has moved, so a stale
reference fails loudly instead of comparing the wrong inputs.
Eight samples cannot support a p50 or p95, and presenting them as one
invites reading a throughput claim into evidence that carries none.
Keep the raw per-probe milliseconds and drop the derived percentiles
and the JVM memory snapshot, which nothing gates on either.
The embedding backend and both framework adapters shipped in 0.3.0 with
no documentation, and the equivalence gate had none outside its own
resource directory.

Adds an Embeddings page covering the backend, pooling, the adapters, and
the gate, wired into nav. Adds the gate to Building and Testing, the
adapters to the Spring AI and LangChain4j guides, and capability cards
to the landing page.

Fixes two real errors found while writing it:

- vectors.adoc imported com.integrallis.models.embedding.EmbeddingBackend,
  which moved to models.api in 0.3.0.
- Four cross-page anchors used Asciidoctor's default _underscore_ id
  form while the site generates kebab-case, so #_rag_with_vectors,
  #_streaming_chat and #_add_vectors_for_rag were already broken. All
  cross-page anchors now resolve.

The LangChain4j RAG example referenced an undeclared embeddingModel; it
now shows where it comes from.
Qwen3-Embedding-0.6B Q8_0 against llama.cpp 6ea215d17: minimum cosine
0.9995014, mean 0.9996470, max component delta 0.005263, max norm
deviation 2.73e-09. Eight probes, last-token pooling, dim 1024.
@bsbodden
bsbodden merged commit fa26f46 into main Aug 6, 2026
6 of 12 checks passed
@bsbodden
bsbodden deleted the feat/embedding-equivalence-gate branch August 6, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant