An embedding can’t tell two age brackets apart

Insights, Updates17 Jul 2026Damian Naglak
260717 damian embeddings

To an embedding model, “adults aged 25 to 34” and “adults aged 35 to 44” are the same audience. Across nine models the two brackets score 0.93 to 0.98 out of 1.0. The number that defines the segment barely registers.

Embedding-based audience matching turns a description into a vector and matches by distance, so closeness of meaning becomes closeness of vectors, scored by cosine. It works for fuzzy intent, and it falls apart on anything defined by a number, and standard targeting is full of those: age brackets, income bands, price caps.

I embedded pairs of standard adjacent audiences that differ by one number, on nine models: Google and OpenAI across three generations, plus open models from 22 million parameters up to an LLM-based one. Every model, every pair, scored between 0.88 and 0.99. Age 25 to 34 against 35 to 44, income 100 to 150k against 150 to 200k, all nearly the same vector.

That number only means something against a baseline, so I added one. On text-embedding-3-large the neighbouring bracket “aged 35 to 44” scored 0.93 against “aged 25 to 34”, while an unrelated audience scored 0.30 and the same bracket reworded scored 0.79. The neighbouring band came out closer than the same band said two ways. The models separate audiences fine, they just cannot separate two that differ by a number.

The reason is in the pieces the model reads. A tokenizer chops text into dictionary fragments, and a number is no exception: “30,000” goes in as “30”, “,”, “000”, and “35,000” as “35”, “,”, “000”. Two of the three pieces are identical, and the one that differs, “30” against “35”, is just another entry in the dictionary. Nothing in it says 35 is larger than 30. The two sit close only because they turned up in similar sentences in training, next to prices and ages. Then the model squashes all those pieces into one fixed vector, so flipping one fragment among a dozen barely moves it, while a word that changes the whole meaning, budget against luxury, moves it much more.

So the model can drop a number in roughly the right neighbourhood by association, but it cannot compare two of them. A bracket is defined by the one thing the model skips, the boundary. That is why the number stays an ID next to the vector and does not fold into it. A vector ranks how close a meaning is. It cannot count.

One caveat: these are general-purpose models trained on the open web, not on ad data. A model trained on ad data would place common price points better, but comparing two numbers is still not something embeddings do.