Open a set of nesting dolls and line them up. The smallest one is tiny, but it is still a whole doll, with a face and a painted dress. Nothing important was left off to make it small.

An from nomic-embed-text is a list of 768 numbers. Every one of them takes 4 bytes to store, for every document you keep. For a million documents that is about 3 GB, just for the embeddings.
So a fair question is: do you need all 768? Could you keep only the first 256, a third of the size, and still find the right documents? Some models lose very little. Others lose more. This lesson measures two of them.

Dimension. One number in an . nomic-embed-text gives 768 of them, bge-m3 gives 1,024.
Shorter embedding. Keep only the first N numbers of the list, then scale them so the length is 1 again. Lesson 7 showed why length 1 matters: then cosine, dot product and distance all agree.
dimensions. A setting you can send to Ollama's /api/embed, which does the cutting for you.
Matryoshka. The Russian name for nesting dolls. It is also the name of a way of training an embedding model so that the first numbers carry the most meaning, and the later ones add detail.
Model card. The page a model's makers publish with it, saying what it is for and how to use it. For many models it lives on Hugging Face, a website that hosts models.
Top 5. The right lesson was among the first five results.
Noise. Small differences that happen by chance. They go away, or flip, if you run the test again with other questions.
This file asks Ollama for the same at four sizes, 16, 64, 256 and all 768 numbers, and checks which of five short texts best matches one question. Here it is, open in my VS Code.

And this is what it printed:

At 16 numbers, the search picked the text about sharding, 0.847 against 0.802 for . Sixteen numbers were too few to tell the topics apart. At 64, 256 and 768 numbers, rate limiting won.
Notice that the scores are higher with fewer numbers: 0.802 at 16, 0.667 at 768. With only a few numbers, every text looks a bit alike, so all the scores bunch up near the top. What matters is not the size of a score but which text scores highest, and by how much.

The search_document: and search_query: labels at the start of each text are what nomic-embed-text expects, as lesson 3 showed.
To run it: install Ollama from ollama.com, open it and leave it running. Run ollama pull nomic-embed-text in a terminal, save the file below as , and run (on Windows: ). You need only Python 3. If you see "Connection refused", open the Ollama app first.
Before trusting the setting, I checked what it does. I asked Ollama for all the numbers, cut them myself, and compared that with what Ollama returned for "dimensions": 256.

They matched to within about one ten-millionth, for both models, on the 50 documents I checked. So the setting does two simple things: it keeps the first 256 numbers and throws the rest away, then it scales what is left back to length 1.

The setting works on any model, including one never trained for it. That means the question "is a shorter good enough?" is really "how much of the meaning is left in the first numbers?"

This uses exactly the setup from lesson 6 (splitting text into pieces):
As a check, bge-m3 at full size should repeat lesson 6's result, and it did: 356 ranked first and 439 in the top 5.
Here is the lab's report, from the terminal. It also includes a small second test, lesson 4's 60 searches in six languages.


These are totals. Question by question, nomic at 256 missed 13 that full size found and found 3 that full size missed. bge-m3 missed 29 and gained 4.

Below 256, the gap grows. At 64 numbers, nomic still found 381 and bge-m3 315. At 32, it was 295 against 207.

The stricter test, ranked first, shows the same shape. And it adds something worth noticing: at full size, bge-m3 is the better model here, 356 to 339. Cut both to 128 numbers and nomic is well ahead, 297 to 254. Which model is "better" depends on how short you need the vectors to be.
Tiny differences, like nomic's 341 at 512 against 339 at full size, are noise. Neither model got better by being cut.
One fairness point. At the same number of numbers, 256, nomic keeps a third of itself and bge-m3 only a quarter. Compared by the share kept, the two are closer: bge-m3 at half its size (512) had 6 fewer than full, nomic at a third (256) had 10 fewer. For storage, though, what you pay for is the number of numbers, so the equal-number comparison is the one that matters for cost.
The model card for nomic-embed-text v1.5 says, in its title, that it was trained to be cut:

"Resizable" and "Matryoshka" mean it was trained so that the first numbers work on their own, like the smallest doll. bge-m3's model card says nothing about being cut. That does not prove how it was trained, only that its makers do not advertise it.
But is the model I tested really v1.5? The name nomic-embed-text does not tell you which version you have. In Ollama, a tag is the part after the colon, like :v1.5, and every model has an ID made from its contents. So I pulled the v1.5 tag and compared the IDs:

Same ID, 0a109f422b47. They are the same model.

That is the idea. To see whether it is true here, I ran the same 470-question test keeping the last N numbers instead of the first. If the meaning is packed at the front, the first numbers should win clearly.

Lesson 4's 60 searches in six languages ran too, with the same cuts.

The direction was the same: bge-m3 fell from 27 to 19 at 256 numbers, and to 7 at 32. But with only 60 searches, and only 10 in English, the numbers jump around. For example, nomic's English searches found 6 of 10 at 256 numbers and 4 of 10 at full size. That is luck, not an improvement. This is why the lesson leans on the 470-question test.

This is simple arithmetic, not a measurement: 1 million documents times the number of numbers times 4 bytes.
Comparing a question with each document also means multiplying fewer numbers. How much faster that makes a real database, this lesson did not measure.
This box holds the real ranks from the test: for each of the 470 questions, where the right lesson came, at 64 numbers, 256 numbers and full size, for both models. Press Run. Then change SIZE to "64" or "full", and TOP to 1.



Measured: two models, six sizes, 470 quiz questions over 1,823 pieces, and 60 short searches in six languages.
Not measured: speed and memory inside a real (a database built to store embeddings and search them), other models, and questions written by real users. The quiz questions were written by the same author as the lessons, which probably makes them easier to match than real questions.

shorter_vectors.py and watch 16 numbers pick the wrong text.
5 questions - Score 80% to pass
What does Ollama's "dimensions": 256 setting do to an embedding?
Keeping the first 64 numbers instead of the last 64 gave nomic 381 against 361 in the top 5, and bge-m3 315 against 309. What does that show?
At full size bge-m3 ranked the right lesson first 356 times and nomic 339 times. At 128 numbers it was 254 against 297. What does that tell you?
In the small example, 16 numbers picked the sharding text for a question about too many requests. What is the lesson from that?
You store 256-number embeddings for your documents. What must you do with each question?
shorter_vectors.pypython3 shorter_vectors.pypython shorter_vectors.py# Ask Ollama for shorter embeddings and see whether search still finds the right text.
# Needs Ollama (ollama.com) running, and: ollama pull nomic-embed-text
import json, urllib.request
def embed(texts, dims=None):
body = {"model": "nomic-embed-text", "input": texts}
if dims:
body["dimensions"] = dims # keep only the first `dims` numbers
req = urllib.request.Request("http://localhost:11434/api/embed", data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req).read())["embeddings"]
texts = ["Rate limiting: cap how many requests each user may send per minute.",
"Caching: keep a copy of data close to where it is used.",
"Load balancing: spread incoming traffic across several servers.",
"Sharding: split one big table across many machines.",
"Message queues: let one service hand work to another later."]
question = "How can I stop one user from sending too many requests?"
for dims in [16, 64, 256, None]:
docs = embed(["search_document: " + t for t in texts], dims)
q = embed(["search_query: " + question], dims)[0]
scores = [sum(a * b for a, b in zip(q, d)) for d in docs] # length 1, so this is cosine
best = max(range(len(texts)), key=lambda i: scores[i])
size = dims or len(q)
print(f"{size:>4} numbers: best match '{texts[best].split(':')[0]}' "
f"(rate limiting {scores[0]:.3f}, best other text {max(scores[1:]):.3f})")
Five texts and one question prove nothing on their own. They only show the idea. The real test comes next.
I also tried the exact recipe on nomic's model card, which adds a step called layer normalisation before cutting. It gave the same results at every size. The vectors I checked already averaged almost exactly zero, so that step barely changed them. Ollama's simpler cut is enough.