🌐 Nolife Langage - Don't say cost. Say investment.
on September 6, 2026
🌐 Nolife Langage - Don't say cost. Say investment.
The number does not change. The frame does.
Cost sounds like something you lose. Investment sounds like something you were going to do anyway, with a return attached. Basic sounds like the diminished option. Essential sounds like the one you need. Standard is generic. Customized was made for you.
I heard the pitch in a sales video:
Don't say $1,000, say $999, it seems cheaper. Don't say buy now, say there's only three left. Never say the word basic, say essential. Never say standard, always say customized. The word few, don't say that one, always say limited. And never use the word cost, always say investment.
Words matter when you are trying to get people to buy. I wanted to know what happens when the reader is not only a human, but also a tokenizer.
The first surprise was German. On this corpus, with the o200k_base encoding, the commercially stronger word maßgeschneidert took five tokens. Standard took one. That is +400%. Unverzichtbar for Basis went 1 → 4. The sales voice got louder. The token meter went the other way.
meaning
|
|
token cost ----+---- persuasion
Those are three different axes. A phrase can win one and lose another. This article is the small Darkwood experiment that tried to keep them separate.
Words already have a price
The six English swaps are not a copy deck. They are a laboratory: small enough to tokenize, semantically interesting enough to argue about.
| Original | Replacement | frame (claimed) |
|---|---|---|
| $1,000 | $999 | looks cheaper |
| buy now | only three left | scarcity |
| basic | essential | necessary, not diminished |
| standard | customized | made for the buyer |
| few | limited | scarcity |
| cost | investment | expected return, not a loss |
I did not A/B a conversion rate. I am not claiming the psychology is settled. I am claiming the substitutions are worth counting.
English cost → investment is the cleanest example: same idea, different frame. On o200k_base it is also a wash for the tokenizer. Both sides measured 1 token. The number did not move. The story still changed.
$1,000 → $999 moved both ways at once: charm pricing and fewer tokens (4 → 2). Scarcity went the other way. buy now is 2 tokens; only three left is 3.
That is already the triangle. Meaning can stay close. Persuasion can get louder. Tokens can go up, down, or nowhere.
Then the model gives words another price
APIs and context windows do not bill in words. They bill in tokens: pieces of text after a tokenizer has split the string. A tokenizer is not a dictionary. It is a compression of byte sequences it has seen often. English is over-represented in that history. Other languages, and even other English spellings of the same idea, can land on more pieces.
Damian Zygmuntowicz’s post on the hidden token cost of languages — Polish przedsiębiorstwo as the provocation — is why I started looking. I did not reproduce Polish counts. Darkwood ships in English, French, and German. Those are the languages I measured.
The meter matters. Characters are not tokens. Words are not tokens. Input tokens are not output tokens. Cache hits are not invoices. A bytes / 4 heuristic is an estimate, not a measurement. I ran actual OpenAI BPE locally with yethee/tiktoken:
- Primary:
o200k_base(GPT-4o family) - Secondary:
cl100k_base(GPT-4 / 3.5-turbo family)
Those are not Claude tokens, not Gemini tokens, and not “universal LLM tokens.” The encoding is part of the result.
Three languages, one idea
I wrote two short parallel passages — a workshop offer and a price line — in English, French, and German. Same meaning, on purpose. Tiny corpus: version 1.0.2, hash 7510f01cf7c3…. Not a statistical study of “French” or “German.” A deterministic laboratory.
Then I counted.
On this corpus with o200k_base, the English version used 46 tokens, French 58 (+26.09% vs English), German 63 (+36.96% vs English).
| Language | Words | Characters | Tokens (o200k_base) |
Tokens / word | vs English |
|---|---|---|---|---|---|
| English | 41 | 240 | 46 | 1.12 | baseline |
| French | 46 | 273 | 58 | 1.26 | +26.09% |
| German | 41 | 298 | 63 | 1.54 | +36.96% |
Those percentages belong to this corpus and this tokenizer. They are not a tax on French, or on German.
German used the same word count as English and more characters. It also used more tokens per word. The bytes / 4 heuristic estimated 60 / 71 / 76 and overestimated every language. It still hid how wide the ranking gap is.
I even tried to make the German more natural. Replacing the calque aufgezeichnete Führung with gefilmten Rundgang cut characters 301 → 298 and raised o200k_base tokens 62 → 63. Fewer characters. One more token. Shorter text is not the story.
It depends on the tokenizer
Same texts, second encoding.
| Language | o200k_base |
vs EN | cl100k_base |
vs EN |
|---|---|---|---|---|
| English | 46 | baseline | 46 | baseline |
| French | 58 | +26.09% | 71 | +54.35% |
| German | 63 | +36.96% | 73 | +58.70% |
English stayed at 46. French jumped from +26.09% to +54.35%. German from +36.96% to +58.70%. The older encoding was harsher on French and German. The language premium is not a property of French. It is a property of this corpus × this encoding.
That is the whole point of running two tokenizers. If I had published only o200k_base, I would have understated how expensive the same French text can look under cl100k_base.
Now change the words
I took the six sales swaps, wrote idiomatic French and German cousins, and labelled two intents:
- persuasion — change the buyer’s frame (may cost more tokens)
- token_opt — say a close idea with fewer tokens (may weaken the frame)
Never silently pick the cheapest wording and call it best. Persuasion here is labelled, not measured. The token_opt lines are laboratory guesses. The FR/DE phrases are drafts, not a translation study.
A few rows on o200k_base:
| Lang | Intent | Original | Replacement | Before | After |
|---|---|---|---|---|---|
| en | persuasion | $1,000 | $999 | 4 | 2 |
| en | persuasion | cost | investment | 1 | 1 |
| en | persuasion | buy now | only three left | 2 | 3 |
| en | persuasion | standard | customized | 1 | 2 |
| de | persuasion | Standard | maßgeschneidert | 1 | 5 |
| de | persuasion | Basis | unverzichtbar | 1 | 4 |
| de | token_opt | maßgeschneidert | passend | 5 | 2 |
| en | token_opt | only three left | 3 left | 3 | 2 |
| fr | persuasion | achetez maintenant | plus que trois en stock | 3 | 5 |
Four corners of the triangle, on this run:
- Stronger sales voice, more tokens:
standard→customized; Germanmaßgeschneidert(+400%). - Same tokens, different frame: English
cost→investment(1 → 1). - Weaker sales voice, fewer tokens:
maßgeschneidert→passend(5 → 2). - Charm pricing that also shrinks BPE:
$1,000→$999(4 → 2).
Scarcity often costs tokens. French achetez maintenant → plus que trois en stock went 3 → 5. Then token_opt compressed it to 3 restants (5 → 3). Cheaper tokens. Less sales voice. Both measurements are real. Only one of them is “better,” and it depends which axis you care about.
The red rows are the interesting ones
Nine of twenty-seven pair rows used more tokens after the rewrite. All nine are persuasion. None are token_opt.
German is where it gets loud:
| Original | Replacement | Before | After | Delta |
|---|---|---|---|---|
| Basis | unverzichtbar | 1 | 4 | +3 |
| Standard | maßgeschneidert | 1 | 5 | +4 |
| Kosten | Investition | 1 | 2 | +1 |
| wenige | begrenzt | 2 | 3 | +1 |
| jetzt kaufen | nur noch drei verfügbar | 3 | 4 | +1 |
That is not a failed benchmark. Persuasion optimization is not token optimization. A commercially stronger word can be tokenizer-hostile. If I had discarded the red rows, I would have published a fairy tale about wording that always saves money.
As a block, persuasion on this corpus went 36 → 45 tokens (−25% “saved,” i.e. it costs more). German persuasion alone: 12 → 20 (−66.67%). English persuasion was a wash (10 → 10). token_opt then recovered tokens, including German (+45.45% as a block). Several token_opt rows cheapen the same idea the persuasion row just made more expensive. That is the control: same ideas, opposite intent.
62 → 62
If I dump every pair into one total, I get this:
| Tokens before | Tokens after | Saved | |
|---|---|---|---|
| persuasion, all | 36 | 45 | −9 |
| token_opt, all | 26 | 17 | +9 |
| mixed, all | 62 | 62 | 0 |
Sixty-two in. Sixty-two out. It looks like wording did nothing.
It did not. Individual substitutions moved in both directions. The mixed aggregate cancelled them. Mixed German is still −13.04% (23 → 26). Mixed English still “saves” 2 tokens. The all-language zero is persuasion −9 plus token_opt +9.
This is a warning against benchmark summaries. An aggregate can erase the phenomenon you are trying to understand. I almost published 62 → 62 as the headline. That would have been the dishonest one.
What does it cost in money?
I projected the pair tokens — not the passages — at one million requests, using a dated gpt-4o-mini snapshot (2 August 2026): $0.15 / $0.60 per million input / output tokens. That is an estimate, not an invoice. This corpus is tiny. One request is fractions of a cent. I am not going to pretend otherwise.
At 1,000,000 requests, all-language:
| Intent | Input saved | Output saved |
|---|---|---|
| persuasion | −$1.35 | −$5.40 |
| token_opt | +$1.35 | +$5.40 |
| mixed | $0.00 | $0.00 |
The mixed $0.00 is the same cancel in dollars. At that million-request scale, German persuasion input alone goes from $1.80 to $3.00 (saved −$1.20). Scale can make a habit material. This particular laboratory is not itself an invoice simulation.
The invisible context tax
The model still has the same context window in tokens. What changes is how much of this natural-language content fits inside it.
I filled a hypothetical 100,000-token window with copies of the parallel offer. Denominator: measured passage tokens, not pair totals. That is not a product context window. French does not get a smaller window; the same token budget simply holds fewer copies of this French text.
On o200k_base:
| Language | Copies that fit | vs English | Lost vs English |
|---|---|---|---|
| English | 2,173.91 | baseline | 0.00 |
| French | 1,724.14 | −20.69% | 449.77 |
| German | 1,587.30 | −26.98% | 586.61 |
The same token window fits less of this French corpus, and still less of this German corpus. Switch to cl100k_base and French vs English becomes −35.21% copies. Encoding changes how many copies fit. Checked: 100,000 / 63 = 1,587.30; 2,173.91 − 1,587.30 = 586.61.
Invoice savings on the pair corpus were tiny. The capacity story is already visible.
Caveman and the cost of optimization itself
I looked at Caveman for this experiment — an open-source project about compressing how coding agents write, not a second benchmark I ran on this corpus. I did not port it. The useful bit is methodological.
Caveman reports real compression and also publishes cases where overhead erases or reverses savings. Shorter output is not automatically lower total cost. Output reduction is not an invoice. An honest delta needs a control: they compare against a terse prompt, not against silence.
That is why I split persuasion from token_opt, and why the nine red rows stay in the article. If the “optimized” path costs more, show it. A benchmark that only prints the green rows is a brochure.
Local o200k_base is still OpenAI BPE executed on my machine. It is not a Claude bill, even though Caveman’s offline evals use the same encoding. Different product. Same hygiene: label the meter, keep the regression.
Building it with Symfony and Darkwood Flow
The experiment is a small Symfony 8.1 console app on PHP 8.5.4. No database, no HTTP API, no frontend. Tokenizer: yethee/tiktoken 1.1.1. Orchestration: darkwood/flow 8.1.6, actually exercised — the command cannot print tables unless Flow ran.
The pipeline is seven jobs:
LoadCorpus
→ TokenizeBefore
→ ApplyCandidates
→ TokenizeAfter
→ Compare
→ EstimateCost
→ Report
Each step stays explicit. Load the two passages and 27 pairs. Tokenize the parallel offer. Record the labelled replacements already sitting in the corpus. Tokenize each pair. Compare, self-check, split aggregates by intent. Estimate cost. Report. The printed trace is the proof: COMPARE: self-check PASS.
php bin/console app:language:benchmark
php bin/console app:language:benchmark --format=json
JSON lands in var/benchmark/. I wanted a measurement I could evolve without hiding the stages in a script named run.php.
PHP 8.6: readonly defaults
PHP 8.6 allows an ordinary instance property to carry a default:
public readonly string $evidence = 'ESTIMATED';
On this machine that is a compile-time error: Readonly property Demo::$evidence cannot have default value. The runtime is PHP 8.5.4. I did not execute the 8.6 feature.
A default on a promoted constructor parameter already worked in 8.1+. The 8.6 RFC lists that as a non-goal: it is a parameter default, not a property default. PricingAssumptions uses that older path. The pricing snapshot stays an immutable config object either way. This sidebar does not change a token count.
PHP has references, not C pointers
A zval is a type tag plus a value. Strings and arrays copy-on-write. $alias = $state copies an object handle (spl_object_id equal: true; both names saw traces 7 → 8). PHP & wraps a zval in IS_REFERENCE / zend_reference. $ref++ incremented the integer ($n became 3). It did not walk memory.
A PHP reference is not simply a C pointer exposed to PHP code. Flow wraps each job return in a new Ip with readonly $data; returning BenchmarkState reuses the handle. Zend uses C pointers internally. Userland PHP does not hand you one. php bin/console app:language:benchmark --explain-values prints that table. No FFI. This sidebar does not change a token count either.
What I learned
Natural languages are not tokenization-equivalent. On this corpus with o200k_base, French used 58 tokens where English used 46, and German used 63. Change the encoding and the premium moves with it.
Persuasion and token efficiency can conflict. Maßgeschneidert is a better sales word and a worse BPE citizen. English investment is a wash. Charm pricing can accidentally help both axes. Scarcity copy often does not.
Character count is a poor substitute for a real tokenizer. The more natural German phrase was shorter in characters and longer in tokens. bytes / 4 overestimated every language and flattened the gap.
Aggregates can hide opposing movements. 62 → 62 is a cancellation, not “word choice does not matter.” Split the intents or you will optimize a zero.
Context efficiency matters even when the pair-corpus invoice is tiny. The same token window fits fewer copies of this French, and fewer still of this German.
Optimization has its own overhead. Publish the red row.
I did not measure live provider usage, conversion, or a professional translation. The next honest experiment, if I run one, is a single provider-reported EN/FR/DE pair on the same passages.
Until then: measure the actual workload before you optimize it. And when the mixed total comes back unchanged, look at the rows.
Sources
- 3 Linkedin posts
- tiktoken-php
- Caveman
- Code source : https://github.com/matyo91/nolife-language
- Slides : https://github.com/matyo91/slidewire