Convergence
in vector space

github.com/pamelafox/convergence-with-vectors

Pamela Fox @ PyBay 2026

About me

Pamela Fox

Principal Cloud Advocate at Microsoft

Formerly: UC Berkeley, Coursera,
Khan Academy, Google

Pamela Fox smiling with an Olaf statue

Let's play Convergence

  1. Two players face each other.
  2. Everyone counts down, "3..2..1"
  3. They both yell a word at the same time,
    like "fire" and "whale".
  4. Everyone thinks:
    "What single word is between those two words?"
  5. Two new players count down and yell their words,
    like "blubber" and "engine".
  6. Same word? Everyone wins! ๐ŸŽ‰
    Different words? Keep going.

Can computers play it?

tinyurl.com/play-convergence

Words as vectors

Vector space diagram with an apples vector and an oranges vector

Vector embeddings

Vector embeddings are lists of numbers that represent items in a high-dimensional space.

Each number is a dimension.

An embedding model turns text into a vector:

"fire" โ†’ embedding model โ†’
[-0.030, 0.089, 0.008, 0.048, 0.026, โ€ฆ]

Bar chart of all 384 dimensions of the fire vector from MiniLM L6, with values between about -0.14 and 0.21 The fire vector drawn as an arrow from the origin, with faint extra axes for more dimensions

Open-weight embedding models

MiniLM L6 MiniLM L12 BGE Small Harrier 270M mxbai Embed Large Qwen3 Embedding 0.6B
PublisherHugging FaceHugging FaceBAAIMicrosoftMixedbreadQwen
Parameters23 M33 M33 M268 M335 M596 M
Dimensions3843843846401,0241,024
Max tokens25612851232,76851232,768
LanguagesEnglishEnglishEnglishMultilingualEnglishMultilingual
MTEB (eng, v2)59.0โ€”64.3โ€”66.370.5
MTEB (multi)41.4โ€”43.866.645.964.3

๐Ÿ”— MTEB leaderboard

Generate embeddings with Python

Download model weights from Hugging Face
and generate embeddings with SentenceTransformer:


from sentence_transformers import SentenceTransformer

model = SentenceTransformer(
    "sentence-transformers/all-MiniLM-L6-v2")

vectors = model.encode(["fire", "whale"],
                       normalize_embeddings=True)
print(vectors.shape)  # (2, 384)
                

๐Ÿ”— Full code: embed.py

Measuring vector similarity

The most popular method is cosine similarity.


fire, whale = model.encode(
    ["fire", "whale"],
    normalize_embeddings=True)
                
fire[-0.030, 0.089, โ€ฆ]
whale[-0.026, 0.061, โ€ฆ]

# Cosine similarity
np.dot(fire, whale) / (
    np.linalg.norm(fire) *
    np.linalg.norm(whale))  # 0.30
                
\[\frac{\color{#c2410c}{\mathbf{x}} \cdot \color{#3776ab}{\mathbf{y}}}{\lVert\color{#c2410c}{\mathbf{x}}\rVert \, \lVert\color{#3776ab}{\mathbf{y}}\rVert}\]

# Dot product for unit vectors
np.dot(fire, whale)  # 0.30
                
Arrows for fire and whale, 72 degrees apart: cosine similarity 0.30

๐Ÿ”— Full code: cosine.py

Most similar words

Rank every vocabulary word by similarity to "fire":

WordSimilarity
fire1.00
flame0.80
firework0.78
wildfire0.71
fireball0.70
Histogram of cosine similarity between fire and the other 4,999 vocabulary words: most fall between 0.2 and 0.35, with flame far out at 0.80

๐Ÿ”— Full code: similarity.py

Where two words meet

Arrows for earring and modem, with a third arrow pointing between them to a question mark

Operator 1: Centroid

Find the word closest to the average vector.


query = normalize(vec_a + vec_b)
scores = np.dot(vocab.vectors, query)
                

Effectively sums s_a + s_b, so one very high similarity can win.

Operator 2: Product

Find the word with the highest product of similarities.


scores = (np.clip(sim_a, 0, None) *
          np.clip(sim_b, 0, None))
                

The biggest rectangle s_a ร— s_b wins, so one low similarity hurts.

Operator 3: Maximin

Find the word whose weaker similarity is highest.


scores = np.minimum(sim_a, sim_b)
                

Maximize the minimum similarity.

Operator 4: Textual

Let the model combine them: embed a phrase.


phrase = f"{word_a} and {word_b}"
phrase_vec = model.encode(
    [phrase], normalize_embeddings=True)[0]
scores = np.dot(vocab.vectors, phrase_vec)
                

The wording of the combined phrase changes the winner:

ab"a and b""the word between a and b"
earringmodemearphoneearphone
firewhalewhalebonewhaler
pianorainpianistrainstorm
banjopistachiohickorymandolin
blubberenginebluebirdmotor

All four operators

The top 5 words for earring + modem, with MiniLM L6:

Playing the game

The game, step by step

  1. Start with two words, a and b.
  2. Two models each pick their top word with the same operator.
  3. Same word? Converged! ๐ŸŽ‰
  4. Different words? Those two picks become the next pair.
  5. Stop if a pair repeats (loop) or after 10 rounds.

The game loop


pair = (word_a, word_b)
for round_number in range(1, max_rounds + 1):
    # Each model picks its top word, excluding the pair
    picks = tuple(top_word(model, operator, pair)
                  for model in (model_a, model_b))
    if picks[0] == picks[1]:
        return "converged", round_number
    if picks in seen:
        return "loop", round_number
    seen.add(picks)
    pair = picks
return "round_limit", max_rounds
                

๐Ÿ”— Full code: simulate_convergence.py

Let's play again!

  • Switch the operator to maximin for hard mode.
  • Try random models each round.
The game's customize panel: starting words, operator set to maximin, and player options for same two models, random models each round, or you vs. a model

tinyurl.com/play-convergence

Embedding model battle

Six embedding models arranged in a circle, with a line between every pair of models

The battle setup

  • 6 models โ†’ 15 pairings (+ each model vs. itself)
  • Same 100 random starting pairs for every pairing
  • Up to 10 rounds per game
  • Outcomes: converged, loop, or 10 round limit

uv run model_battle.py --games 100 --operator centroid
                

Runs in ~20 seconds: the vectors are precomputed in SQLite.

Self-play: instant convergence

The game with MiniLM L6 playing itself: starting from fire and whale, both players say flame and converge in 1 round

A model vs. itself converges in round 1, every time.

Same vectors + same math = same word.

Convergence is only interesting when the players see the world differently.

Which models play well with others?

Convergence rates with the centroid operator:

๐Ÿค Most

MiniLM L6 + MiniLM L1296%
MiniLM L12 + BGE Small96%
MiniLM L12 + mxbai94%
BGE Small + mxbai94%

๐Ÿ™… Least

BGE Small + Qwen375%
MiniLM L12 + Qwen379%
mxbai + Qwen380%
BGE Small + Harrier81%

Similar small models think alike; Qwen3 is the odd one out.

Best benchmark โ‰  best teammate

ModelConverges vs. othersAvg roundsLoopsMTEB (eng)MTEB (multi)
MiniLM L690%2.410%59.041.4
MiniLM L1290%2.410%โ€“โ€“
BGE Small88%2.512%64.343.8
mxbai Embed Large88%2.611%66.345.9
Harrier 270M84%3.016%โ€“66.6
Qwen3 Embedding 0.6B81%3.318%70.564.3

The highest MTEB scores converge the least.
Benchmarks measure their tasks, not yours!

Does the operator matter?

OperatorConvergedAvg roundsLoopsRound limit
Centroid87%2.713%0%
Textual86%2.814%0%
Product84%3.114%2%
Maximin51%4.522%26%

The longest roads to convergence

Centroid

RoundMiniLM L6Qwen3
Startbanjopistachio
1piebanana
2fruitpan
3fruitcakebanana
4fruitpapaya
5mangopeach
6fruitpomegranate
7watermelonstrawberry
8fruitblueberry
9strawberrystrawberry ๐Ÿ“

Maximin

RoundMiniLM L12Qwen3
Startnetwood
1webboard
2forumplatform
3arenaboard
4chessgame
5hockeyplayer
6athleteball
7basketballfootball
8soccerball
9baseballfootball
10basketballbasketball ๐Ÿ€

We converged!
Now what?

Vectors have a day job

Vector embeddings are the technology powering:

๐Ÿ”Semantic search
Find documents by meaning, not keywords
๐Ÿค–RAG
Retrieve relevant context for LLM answers
๐Ÿ›๏ธRecommendations
Suggest items similar to ones you liked
๐ŸงนClustering
Group similar items, spot duplicates

But embedding models differ

Each model captures one way of looking at the world:

One world seen by three embedding models, each drawn as a differently colored globe. Under each, the nearest vectors to 'fire': MiniLM L6 says flame, firework; BGE Small says flame, smoke; Qwen3 says wildfire, flame. The Spanish word 'fuego' added to each cluster: far from 'fire' for MiniLM L6, closer for BGE Small, and right inside the cluster for Qwen3.

Evaluate models for your scenario before you pick one.

One app = one world view

Most apps ship with a single embedding model.
Every user, query, and document goes through it.

Five different people, each with an arrow pointing into the same single embedding model inside your app.

One community = many world views

Each of us brings our own way of looking at the world.

A group of conference attendees posing together after playing an improv game.

Keep playing with humans!

Thank you