Tokens And Embeddings

What an Embedding Is: Meaning as a List of Numbers

0 of 15 complete

0%

Contents

Back|Tokens And EmbeddingsWhat an Embedding Is: Meaning as a List of Numbers
1/15
34 min left
Prerequisites
How a Tokenizer Learns Its Pieces: Byte Pair Encoding, Built From Scratchrequired
Related Topics
Chunking: The First Lever on Retrieval QualityRetrieval and RAG in ProductionIs There Really a Best Chunk Size? Measured on This Course's Own LessonsRetrieval and RAG in Production
1 of 15

Meaning as a Place on a Map

A computer cannot compare meanings. It can only compare numbers. So how does a search engine know that "reset my password" and "I forgot my password" ask the same thing, when they share only one word?

The answer is an .

An illustration of an engineer pointing at a tight cluster of coloured pins on a large wall map while colleagues watch. A line says an embedding is the position of a sentence's pin, written as numbers.

Think of a huge map. Every sentence gets a pin. Sentences that mean the same thing get pins close together. Sentences about different things get pins far apart. To find what someone means, you look for the pins nearest to theirs.

An embedding is the position of that pin, written as numbers. In this lesson I measure two real embedding models on my laptop, and test the thing that matters most for a global audience: do they understand other languages?

The Words You Need First

If a word below is new, read its line.

A hand-drawn word list. Embedding: a list of numbers for the meaning. Embedding model: turns text into an embedding. Dimensions: how many numbers in one embedding. Cosine similarity: how close two embeddings point. Multilingual: trained on many languages. Ollama: runs AI models on your own computer.

. A list of numbers that stands for the meaning of a piece of text. Also called a vector, which is why the code below names its lists vectors.

Embedding model. A program that turns text into an embedding. It is a kind of language model, trained to put similar meanings close together.

Dimensions. How many numbers are in one embedding. Each model has a fixed number.

. A score for how close two embeddings point. Near 1 means very close in meaning. Lower means further apart.

Multilingual. Trained on many languages, so the same meaning in two languages lands in the same place.

Ollama. A free program that runs AI models on your own computer. I used it for every measurement here.

What an Embedding Looks Like

Here is a real one. I gave nomic-embed-text, a popular free model, one sentence: "The server is slow today because many users are online."

The first 8 of the 768 numbers nomic-embed-text returned for the sentence The server is slow today because many users are online, shown as boxes, starting -0.0041 and +0.0737. A note says bge-m3 gives 1,024 numbers for the same sentence, and two models' embeddings can never be compared.

It returned 768 numbers. The first eight are shown above. No single number means anything on its own. The meaning is in the whole list: where it points.

A different model gives a different list. bge-m3, the second model in this lesson, returns 1,024 numbers for the same sentence. Each model draws its own map, so numbers from two models do not match up. Never compare them.

A sequence diagram: two texts go to the embedding model in Ollama, two lists of numbers go to the cosine step, it compares their directions, and one score comes back. Near 1 means the same meaning.

Comparing Two Embeddings

To compare two , we measure the angle between them. The score is called .

A hand-drawn sketch of embeddings as arrows. Two arrows for sentences with the same meaning point almost the same way, a small angle. Two arrows for unrelated sentences point far apart, a wide angle. A note says in the real test same meaning scored 0.889 or more and unrelated at most 0.598.

Imagine each embedding as an arrow from the centre of the map. If two arrows point the same way, the score is 1. The further apart they point, the lower the score.

The maths is simple. Multiply the two lists number by number and add it all up. Then divide by the length of each arrow. An arrow's length is the square root of the sum of each of its numbers squared. The code in this lesson does exactly that in two lines.

Same Meaning, Same Topic, Unrelated

Before running anything, I wrote nine sentence pairs in three groups:

  • Same meaning: "The server is slow today." and "Today the server is responding slowly."
  • Same topic: "The server is slow today." and "We added more memory to the database server."
  • Unrelated: "The server is slow today." and "My grandmother makes excellent bread."

Here is what nomic-embed-text scored.

A dot chart of nine sentence pairs scored by nomic-embed-text, in three columns. Same meaning 0.889 to 0.956, same topic 0.707 to 0.763, unrelated 0.523 to 0.598. The three groups do not overlap.

Same meaning scored 0.89 to 0.96. Same topic scored 0.71 to 0.76. Unrelated scored 0.52 to 0.60.

The three groups do not overlap. That is what makes search work: the right answer scores higher than a sentence that only shares a topic.

But notice that unrelated sentences did not score near 0. They scored above 0.5. Scores from one model are only meaningful compared with each other, not as a fixed scale.

A real terminal recording of embed_facts.py replaying the stored nomic-embed-text run: 768 numbers per embedding, the nine sentence pair scores, the same sentence in six languages scored 0.469 to 0.700 against English next to 0.578 for an unrelated English sentence, and database against five words.

Words Have Neighbours Too

Single words get too. I compared "database" with five other words.

Horizontal bars for the word database against five words with nomic-embed-text: storage 0.693, table 0.671, server 0.616, banana 0.541, happiness 0.527.

With nomic-embed-text, "storage" and "table" came closest, then "server". "Banana" and "happiness" came last. The model has learned which words live in the same part of the map.

The Test That Matters: Other Languages

Readers of this course write in many languages. So I took one English sentence, "The server is slow today because many users are online.", and my own translations of it into French, Spanish, German, Japanese, Hindi and Bengali. The meaning is the same in all seven.

A good should put all seven pins in the same place.

Two sets of bars for one sentence in six languages, each scored against the English original. nomic-embed-text: French 0.547, Spanish 0.563, German 0.700, Japanese 0.553, Hindi 0.537, Bengali 0.469, and an unrelated English sentence about bread 0.578. bge-m3: French 0.967, Spanish 0.932, German 0.979, Japanese 0.922, Hindi 0.953, Bengali 0.921, and the bread sentence 0.429.

With nomic-embed-text, the translations scored 0.47 to 0.70 against the English sentence. An unrelated English sentence about bread scored 0.58. French, Spanish, Japanese, Hindi and Bengali all scored below the sentence about bread. Only German, at 0.70, came near the same-topic pairs.

In other words, this model does not see that a Hindi sentence means the same as the English one.

Two panels for the Hindi sentence against the English one: 0.537 with nomic-embed-text, where the unrelated bread sentence scored 0.578, and 0.953 with bge-m3, where bread scored 0.429.

A Multilingual Model Fixes It

Then I ran exactly the same test with bge-m3, a model trained on many languages. Its own page says it supports more than 100. Ignore the other words on that page; only the size and the language line matter here.

A real screenshot of the bge-m3 page on ollama.com. It describes BGE-M3 from BAAI, shows ollama pull bge-m3 and a size of 1.2 GB with an 8K context, and says it supports more than 100 working languages.

Here is its real run.

A real terminal recording of embed_facts.py replaying the stored bge-m3 run: 1,024 numbers per embedding, the sentence pairs with same meaning 0.929 to 0.955 and unrelated 0.428 to 0.475, and every translation scoring 0.921 to 0.979 against English, next to 0.429 for the unrelated sentence.

Every translation scored 0.92 to 0.98. The unrelated English sentence scored 0.43. Now the same meaning lands in the same place, whatever the language.

Two panels comparing the models. nomic-embed-text: 274 MB, 768 numbers, translations 0.469 to 0.700. bge-m3: 1.2 GB, 1,024 numbers, translations 0.921 to 0.979. A note says the multilingual model is bigger and stores more numbers per text.

bge-m3 also kept the English results in order: same meaning 0.93 to 0.96, same topic 0.63 to 0.70, unrelated 0.43 to 0.48.

The cost: bge-m3 is a 1.2 GB download against 274 MB for nomic-embed-text, and each holds 1,024 numbers instead of 768, so it takes more space to store.

An isometric row of four blocks joined by arrows: 7 sentences, one per language, with the same meaning, Ollama with two models, a cylinder of embeddings of 768 or 1,024 numbers, and the scores against English.

Three Mistakes That Break Embeddings Silently

When go wrong, you see no error message. The search just returns worse results. These three mistakes cause most of it.

1. Mixing two models. A document is any piece of text you want to search, and your store is where you keep their embeddings. Every document must be embedded by the same model as the question. I tried mixing them on purpose: nomic's 768 numbers against bge-m3's 1,024, for the same sentence. The code stopped with no error. Python's zip silently dropped the extra 256 numbers, and the score came out as -0.007, which means nothing. Check that both lists have the same length. If you change models, embed every document again.

2. Forgetting a model's instructions. Some models expect a short label in front of the text. nomic-embed-text expects "search_query: " before a question and "search_document: " before a document. (When comparing two sentences with each other, as this lesson does, the same label goes on both.) I embedded "How do I reset my password?" with and without its label. The two embeddings scored only 0.96 against each other, not 1.0. So the label changes the embedding, and leaving it out changes your results. Read the model's page before you use it.

3. Trusting the English score for other languages. This lesson's main finding. A model that ranks English sentences well can still fail completely on Hindi or Japanese. Test the languages your users actually write in.

Run It on Your Own Machine

Here is a short script that runs both models on French, Hindi and an unrelated sentence. This is the real file, open in my VS Code.

A real screenshot of compare_languages.py open in VS Code. It sends texts to Ollama's embed endpoint, adds nomic's search_query label, computes cosine similarity in two lines, and compares an English sentence with French, Hindi and an unrelated sentence for nomic-embed-text and bge-m3.

And this is what it printed. The numbers match the lab exactly, because the script uses the same label for nomic.

A real screenshot of VS Code's terminal after running python compare_languages.py. nomic-embed-text: French 0.547, Hindi 0.537, unrelated 0.578. bge-m3: French 0.967, Hindi 0.953, unrelated 0.429.

To run it yourself:

  1. Install Ollama from ollama.com and start it.
  2. In a terminal, run ollama pull nomic-embed-text and ollama pull bge-m3.
  3. Save the file below as compare_languages.py, as UTF-8 text (the default in VS Code).
  4. Run python3 compare_languages.py (on Windows: python compare_languages.py).

It needs no extra Python library. I typed python because of my own setup, shown as "(venv)" in the screenshot; python3 does the same. If you see "Connection refused", Ollama is not running: start it first. The first run is slower while each model loads.

# Does an embedding model understand other languages? Test it yourself.
# Needs Ollama (ollama.com) running, and:  ollama pull nomic-embed-text  and  ollama pull bge-m3
import json, math, urllib.request

# nomic-embed-text expects a short label in front of each text; bge-m3 does not
LABEL = {"nomic-embed-text": "search_query: ", "bge-m3": ""}

def embed(model, texts):
    texts = [LABEL[model] + t for t in texts]
    body = json.dumps({"model": model, "input": texts}).encode()
    req = urllib.request.Request("http://localhost:11434/api/embed", data=body,
                                 headers={"Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req).read())["embeddings"]

def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / math.sqrt(sum(x * x for x in a) * sum(y * y for y in b))

english = "The server is slow today because many users are online."
others = {
    "French": "Le serveur est lent aujourd'hui parce que beaucoup d'utilisateurs sont en ligne.",
    "Hindi": "आज सर्वर धीमा है क्योंकि बहुत से उपयोगकर्ता ऑनलाइन हैं।",
    "unrelated": "My grandmother makes excellent bread.",
}

for model in ["nomic-embed-text", "bge-m3"]:
    vectors = embed(model, [english] + list(others.values()))
    print(model)
    for name, v in zip(others, vectors[1:]):
        print(f"  {name:<10} {cosine(vectors[0], v):.3f}")

Cosine Similarity by Hand

This box runs in your browser. It holds three tiny made-up of 3 numbers each, instead of real ones with 768, so you can see the sum work. Press Run.

Then change the numbers in pizza to be closer to cat, and run it again.

When to Care About This

A decision flowchart. Do users write in more than one language? If yes, use a multilingual model tested on their text. If no: is storage tight? If yes, a smaller English model; if no, a multilingual model, which works for English too. A note says test on your users' real text.

  • Your users write in more than one language. Test your model on their real text, the way this lesson does. An English-only model can silently fail.
  • Questions and documents are in different languages. A user asks in Spanish, your documents are in English. A multilingual model can match them directly. The other route is to translate the question first.
  • Everything is English. A smaller English model may be enough, and cheaper to store.

Three cards with real logos: Ollama ran both models locally, Python computed cosine in two lines, and VS Code is where the script ran. A note says no paid service was used and both models are free to download.

A hand-drawn sketch titled a multilingual model can match them directly: a question in Spanish goes to an English-only model, which misses the English documents, and to a multilingual model, which matches them. The Ollama logo sits below. A note says this is a sketch; the lesson measured language scores, not a full search.

What This Lesson Measured, and What It Did Not

Two columns. Measured: two embedding models, nine English sentence pairs, one sentence in seven languages. Not measured: search on real documents, other models, a benchmark per language.

Measured: two models, nine English sentence pairs, one sentence in seven languages, and one word against five others.

Not measured: search quality on real documents, other models, or other sentences. The translations are mine, one sentence per language, so treat them as an example, not a benchmark.

What to Do Next

A hand-drawn list of four things to do: score your own sentences, compare scores from one model only, test your users' languages first, and choose a multilingual model if more than one language is involved.

  1. Run the script on your own sentences.
  2. Compare scores within one model only. A 0.6 from one model is not a 0.6 from another.
  3. Test your users' languages before you choose a model.
  4. Pick a multilingual model if more than one language is involved.

The numbers to keep: the same Hindi sentence against English scored 0.537 with nomic-embed-text and 0.953 with bge-m3. Test your users' languages before you choose.

Knowledge Check

Knowledge Check

4 questions - Score 80% to pass

Q1

What is an embedding?

Q2

With nomic-embed-text, unrelated sentences scored above 0.5. What does that tell you?

Q3

With nomic-embed-text, a Hindi sentence scored 0.54 against the same sentence in English, and a sentence about bread scored 0.58. What does this mean?

Q4

Can you compare an embedding from nomic-embed-text with one from bge-m3?

Add your own language to others, and see how each model scores it.