Vectors
-
Four Embedding Models, All 1024 Dimensions, All Incompatible
If you run a vector database, you’ve probably got a startup check that compares your configured embedding dimension against the collection’s actual vector size. Mine has two of them. They’re both useless for the failure I care about.
Here are four embedding models I could plausibly point my memory server at, with the dimension each one emits:
Model Dimensions mistral-embed1024 @cf/baai/bge-large-en-v1.51024 bge-m31024 Qwen3-Embedding-0.6B1024 I checked the last two against their model configs rather than trusting my own commit note, and
hidden_sizeis 1024 in both. Mistral and Cloudflare’s BGE I’d already written into a comparison table months ago without noticing what the column was telling me.So: swap providers, keep the number, and every dimension check on the system passes. Qdrant accepts the writes. The server boots clean. Preflight is green. And your recall results are garbage, because a vector from
mistral-embedand a vector frombge-m3are both 1024 floats that mean entirely unrelated things. Cosine similarity between them isn’t wrong so much as it’s meaningless, and meaningless similarity still returns a top-k. You get five confident results that have nothing to do with the query.That’s a bad failure mode. It’s silent, it survives every check, and the symptom (bad recall quality) looks like a tuning problem rather than a data problem. You’d spend an afternoon adjusting
RECALL_MIN_SCOREbefore you’d suspect the collection.Dimension is a checksum with one byte
The mistake I made was treating dimension as an identity check when it’s a shape check. It tells you the vectors will fit. It says nothing about where they came from.
Think about what actually has to match for a vector search to mean anything. The model, obviously. But also the provider serving it, because a self-hosted
bge-m3and a hosted one behind an inference API can differ in pooling or normalization. The distance metric, since cosine and dot product rank differently on unnormalized vectors. And the dimension, which is the only one of those four I was checking.So the collection now carries a fingerprint: provider host, model, dimensions, distance, and a schema version, written into Qdrant as a well-known point. It’s non-secret by design, no keys, no account IDs, just enough to answer “did these vectors come from the same place the current config points at?” It’s validated at startup and again in preflight, and a mismatch is fatal with its own exit code, 9.
Nine is deliberately separate from the two dimension codes I already had. 4 is config disagreeing with Qdrant. 8 is config disagreeing with what the provider actually returned. 9 is the case where 4 and 8 both pass, every number agrees, and the vectors are still incompatible. Those want three different fixes, so they get three different exit codes. Telling failures apart by status instead of by grepping a log message is a habit I keep being glad about.
The hard part wasn’t detecting it
Writing the check took an afternoon. Deciding what to do about the collection that already existed took longer.
I have a live collection with real memories in it, created before any of this existed. It has no fingerprint. The naive implementation treats that as a mismatch and refuses to boot, which would mean shipping a change that bricks my own running server and everyone else’s, in defense of a problem none of them currently have.
So an absent fingerprint is a third state, not a failure. The server boots normally, preflight succeeds and appends
fingerprint=unadoptedto its ok line, and you get a one-line notice at boot. Nothing breaks. You’re just told.Adoption is then an explicit command,
mem0-mcp --adopt-fingerprint, with two rules I’d encourage you to steal if you build something similar:- It refuses to relabel a collection that already records a different fingerprint. Adoption is for unlabeled collections. If there’s a label and it disagrees with your config, that’s exactly the mismatch the feature exists to catch, and letting a flag overwrite it turns the safety check into a suggestion.
- Auto-stamping only happens on a collection that’s verifiably empty. An empty collection has no vectors to be wrong about, so stamping it is free. A populated one requires you to say out loud that you know where those vectors came from.
The general principle: when you add a validation to a system that’s already running, the pre-existing state needs a name that isn’t “invalid.” Otherwise the check is unshippable, and an unshippable check gets loosened until it stops checking.
The bit I’d get wrong again
I want to be honest about where this came from, because it wasn’t foresight. I was adding Cloudflare Workers AI as a second embedding provider. It’s an OpenAI-compatible REST endpoint, so it needed no adapter at all, just a different base URL, model and token. A config change, nothing more.
I was writing the provider comparison table for the docs, put
1024in the Mistral column and1024in the Workers AI column, and sat there looking at it. The whole feature exists because I typed the same number twice in a markdown table.If I’d added a provider at 768 or 3072, the dimension check would have caught the swap on the first boot, I’d have fixed my config, and I’d never have learned the check was load-bearing for the wrong reason. The collision is what made the gap visible. 1024 is a popular number, and popular numbers are where your shape checks quietly stop being identity checks.
Go look at your own table. If two rows have the same dimension, you have this bug too, and nothing in your stack is going to tell you about it.
Sources
- Mistral embeddings —
mistral-embed, 1024 dimensions - Cloudflare Workers AI: bge-large-en-v1.5 — model card and dimensions
- Workers AI OpenAI compatibility — why no adapter was needed
- Cloudflare AI Gateway — the optional proxy layer in front of the provider
- Qdrant documentation — collections, vector params and distance metrics
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].