Gate releases on embedding equivalence with llama.cpp - #107
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
0reproduced,1not reproduced,2usage or integrity problem.Two floors, because cosine alone is not enough
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 — asvectorsdoes — 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
2with both digests printed.Notes
MINIMUM_COSINEmirrorsModelEmbeddingQualification.MINIMUM_ORACLE_COSINEin 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.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