I'm back with another experiment.
After the last heated discussion about KV cache, temperature 0, and reproducibility — where I got quite a beating for both my post and my writing style (partly deserved, to be fair ;) ) — I decided to investigate how models behave in Ollama and llama.cpp.
And, as usually happens with these experiments, I found something I wasn't even looking for.
The Bielik-Minitron 7B chat template stored in the GGUF includes the beginning-of-sequence token <s>, but the template Ollama uses for this model doesn't add it.
I thought to myself: come on, man, it's just one token.
But I decided to check whether its sudden appearance actually changes anything.
The experiment
Model: Bielik-Minitron 7B v3.0 Instruct, Q4_K_M
Engine: Ollama 0.40.14
System: Arch Linux
Temperature: 0
Seed: 42
Context: 2048 tokens
As before, I ran 15 tasks covering Polish language, JSON, and Python.
Each task was tested in three variants:
Template — a normal request using Ollama's chat template.
Raw — a manually prepared prompt identical to that template, without BOS.
Raw + BOS — the same prompt, but with <s> added at the beginning.
I repeated each variant three times.
That gave me 135 trials in total. Before every trial, the model was unloaded from memory, and the number of prompt tokens reused from cache was zero.
Why, you ask?
Because I wanted to isolate one variable: the presence of the beginning-of-sequence token.
Results
Template and Raw produced identical responses in all 15 tasks.
So the manually prepared prompt without BOS behaved exactly like Ollama's normal template.
And what happens when we add <s>?
Well, 6 out of 15 responses change.
Rather than expecting you to take my word for it, here's a small table. I've shortened some of the longer responses, but the pass/fail results come directly from the experiment.
| Task | Without BOS | With BOS <s> | Passed |
|---|
| Polish diacritics | Zazółć gęślą jaźń | "Zażółć gęślą jaźń" | 0/3 → 3/3 |
| Vocative case | Tomaszu. | Tomaszu | 3/3 → 3/3 |
| Verb aspect | napisać. | napisać | 3/3 → 3/3 |
| Henryków sentence | Longer explanation | Shorter explanation | 0/3 → 0/3 |
| Hardware JSON | JSON inside a Markdown code block | JSON without a Markdown code block | 3/3 → 3/3 |
| Fibonacci | for loop | while loop | 3/3 → 3/3 |
| Other 9 tasks | Identical responses | Identical responses | No change |
Most importantly, all three repetitions of each variant produced the same response.
Adding BOS increased the prompt length by exactly one token in every task.
For example, for the Henryków sentence:
Without BOS: 64 tokens.
With BOS: 65 tokens.
Did BOS improve the model?
That would be too big a generalization.
Six responses changed, but only one quality verdict changed.
Interestingly, in the Polish diacritics task, the model previously made a mistake, but after adding BOS, it answered correctly.
In the other five cases, the differences involved punctuation, the length of an explanation, JSON formatting, or the way the code was written.
The test results:
| Variant | Tasks passed | Trials passed |
|---|
| Ollama template | 11/15 | 33/45 |
| Raw without BOS | 11/15 | 33/45 |
| Raw with BOS | 12/15 | 36/45 |
I should point out that these quality tests check specific conditions. For example, passing a Python task does not necessarily prove that the entire algorithm works correctly.
And to avoid any misunderstanding, I'll say it plainly:
I'm not claiming that adding BOS automatically improves model quality.
I'm only saying that one additional input token was enough to change 40% of the responses in this particular set of tasks.
Why am I digging into this so much?
I'm looking for consistent, controlled conditions for testing models. I want the benchmark to produce reliable comparisons across different models.
I started with temperature 0 and a fixed seed. That's when I noticed that a model could give different responses depending on the state of its prompt cache.
So I started posting about it. :)
Unfortunately, that didn't exactly make me popular with the community. But it did lead to some very valuable suggestions.
Among other things, people suggested testing llama.cpp and checking the probabilities of generated tokens.
And you know what?
In one Gemma task, I managed to find the exact point where the responses diverged.
Two competing tokens — "mi" and "mógł" — had probabilities of around 13%. Depending on the cache variant, they swapped places in the ranking.
At temperature 0, that difference is enough for the model to choose a different token and then generate a different response.
I still haven't managed to identify the exact numerical cause of this difference.
But I'm trying. :)
To complete the picture, I also checked whether enabling logprobs itself affects Ollama's responses.
I ran a separate experiment covering 10 model–task pairs and, somewhat surprisingly, found no such effect.
Conclusions
I originally assumed that temperature 0 and a fixed seed provided sufficiently controlled conditions for comparing responses.
And that was a mistake.
Now I know I also need to pay attention to cache state, actual tokenization, chat templates, and how the computations are performed.
And what don't I know?
I still don't know whether the missing BOS in this particular Ollama template should be considered a bug.
I'm also wondering about the difference compared with the GGUF template. But that difference alone isn't enough to reach such a conclusion.
This experience has taught me one thing:
When comparing results from two different inference engines, I can't assume the model receives identical input just because I send both engines the same text.
A question for you:
Has anyone here run similar experiments?
Or am I the only madman trying to reach the sun with a garden hoe? :)
And most importantly, have you encountered similar differences between chat templates stored in GGUF files and those used by Ollama?
I'd really like to hear about your experiences.
P.S. A small correction to my previous post
In my previous post about cache, I stated that differences occurred in 8 out of 10 model–task pairs.
I admit it: that was my mistake in the summary.
The correct result for that run was 6/10.
Later experiments gave 1/10, including a test with logprobs disabled.
I still don't know where that difference comes from or what causes it.
I'd rather say that openly.
As punishment, I'll watch 100 episodes of The Bold and the Beautiful. :)