fs90's picture
Gemma 3 tokenizer vocabulary in reviewable .spm text form
4ae37b3 verified
|
Raw
History Blame Contribute Delete
7.24 kB
---
license: gemma
tags:
- tokenizer
- sentencepiece
- vocabulary
- gemma
library_name: splintr
---
# Gemma 3 tokenizer vocabulary, as reviewable text (`.spm`)
Google's Gemma 3 `tokenizer.model` converted, id for id, into a plain-text
format that a human can read, diff and audit β€” and that a tokenizer can load
without a protobuf dependency.
**This is a modified file.** See [Modification notice](#modification-notice)
below and the `NOTICE` file. **Use is subject to the Gemma Terms of Use**,
including the Section 3.2 use restrictions β€” see [Licence](#licence).
Nothing here is a model. This is a tokenizer vocabulary: the list of pieces a
tokenizer splits text on, with the scores that decide merge order.
The same conversion for Gemma 2 is at
[`fs90/gemma-2-tokenizer-spm`](https://huggingface.co/fs90/gemma-2-tokenizer-spm).
## Why this exists
A SentencePiece `tokenizer.model` is a protocol buffer. That makes it opaque:
you cannot diff two of them, grep one, or see in a pull request what a change
did. It also means anything that wants to read one needs a protobuf parser and
the SentencePiece schema.
The obvious alternative β€” the `.tiktoken` format, `base64(token) rank` per line
β€” is **lossy for SentencePiece**, in three separate ways:
1. **Scores are destroyed.** SentencePiece merges by score, not by id order.
Recovering merge order from id order is not an approximation, it is
sometimes an inversion.
2. **Byte-fallback spelling is destroyed.** A real SentencePiece piece is
spelled `<0x41>`; storing the raw byte forces a reader to _reconstruct_ that
spelling by scanning for a run of 256 consecutive ids.
3. **Piece type is destroyed.** SentencePiece matches `USER_DEFINED` pieces
verbatim, before merging; they are never merge candidates. `CONTROL` pieces
are never matched from text at all. Both score `0.0` and both are spelled
`<...>`, so neither the score nor the spelling tells them apart.
**Gemma 3 is the vocabulary where that third point bites hardest.** It declares
**6,410** `USER_DEFINED` pieces β€” HTML markers such as `<blockquote>`, and, unlike
Gemma 2, the whitespace and newline runs (`\n`, `\n\n`, …, and runs of spaces).
Drop the type and every one of them is re-merged from its parts: `<blockquote>`
becomes `<` + `blockquote` + `>`, and an indent becomes several shorter runs.
Measured against `sentencepiece` over 1,380 real documents of English and mixed
source code, that mistokenized **11.0%** of them. The same test on Gemma 2, with
only 245 user-defined pieces, was 5.6%.
This format keeps all three.
## Format
One line per token id, in ascending id order, no gaps:
```
<base64 of the piece, UTF-8 encoded> <score> <type>
```
```
PHBhZD4= 0.0 3 # <pad> score 0.0 CONTROL
PGVvcz4= 0.0 3 # <eos> score 0.0 CONTROL
PGJvcz4= 0.0 3 # <bos> score 0.0 CONTROL
```
- **piece** β€” SentencePiece's own `id_to_piece(i)`, so `<0x41>` keeps its real
byte-fallback spelling and `▁` word-boundary runs keep theirs. Base64 because
a piece may contain spaces, newlines or invalid-looking bytes.
- **score** β€” `get_score(i)`, written as the shortest decimal that round-trips
the IEEE-754 value.
- **type** β€” SentencePiece's own `ModelProto.SentencePiece.Type` enum:
`1` NORMAL, `2` UNKNOWN, `3` CONTROL, `4` USER_DEFINED, `6` BYTE.
The id is the line's position, so ids cannot be duplicated or non-monotonic by
construction β€” there is no id field to disagree with the ordering.
## What is in this vocabulary
| | |
| ------------ | ----------------------------- |
| pieces | 262,144 |
| NORMAL | 255,474 |
| USER_DEFINED | 6,410 |
| BYTE | 256 |
| CONTROL | 3 (`<pad>`, `<eos>`, `<bos>`) |
| UNKNOWN | 1 (`<unk>`) |
Two properties worth knowing before you write a loader:
- **`add_dummy_prefix` is `false`.** Gemma does not prepend a word-boundary
marker to the input, unlike Llama and Mistral. Prepending one anyway shifts
the first piece of every input to a different token.
- **`byte_fallback` is `true`**, and the 256 `<0xNN>` pieces are how it is
reached.
## Relationship to other Gemma generations
- **EmbeddingGemma** ships this exact vocabulary β€” all 262,144 pieces *and*
scores are byte-identical, so this file serves it too.
- **Gemma 2** is a genuinely different vocabulary: 256,000 pieces, and only 245
user-defined ones.
- **Gemma 4** shares this generation's merge structure but reassigns 6,206
marker ids, promoting reserved `<unusedN>` slots into named markers. It is
**not** interchangeable with this file, and it is licensed separately under
Apache-2.0.
## Verifying this file
The conversion is checked in both directions before the file is written β€” every
piece, score and type is read back and compared against the source model, and
each score is round-tripped through `f32` to confirm it survives a
single-precision parse. To repeat that yourself against your own copy of
Google's `tokenizer.model`:
```bash
python extract_spm_vocab.py --model tokenizer.model --output gemma3.spm --verify
```
The script is [`scripts/extract_spm_vocab.py` in
splintr](https://github.com/ml-rust/splintr). Any SentencePiece implementation
will do the same job; the format is simple enough to re-derive in a few lines.
## Using it
```python
import base64
pieces, scores, types = [], [], []
for line in open("gemma3.spm"):
b64, score, kind = line.split()
pieces.append(base64.b64decode(b64).decode("utf-8"))
scores.append(float(score))
types.append(int(kind))
# USER_DEFINED (4) pieces are matched verbatim, never merged.
user_defined = {p for p, t in zip(pieces, types) if t == 4}
```
## Provenance
Extracted from Google's Gemma 3 `tokenizer.model`, MD5
`00d2276cbec4474f6cf3df98fbc18cbb`.
The vocabulary is Google's, not this repository's, and keeps Google's licence.
## Modification notice
Required by Section 3.1 of the Gemma Terms of Use, and repeated in `NOTICE`:
`gemma3.spm` is a **modified** form of Google's Gemma 3 `tokenizer.model` β€” it
is not the original file. The protocol-buffer model was converted, id by id,
into the plain-text format described above. Nothing was added, removed,
reordered or rounded: all 262,144 pieces keep their ids, scores and piece types,
and no vocabulary entry differs from Google's file in any way.
## Licence
Gemma is provided under and subject to the **Gemma Terms of Use**, found at
[ai.google.dev/gemma/terms](https://ai.google.dev/gemma/terms) and reproduced in
full in the `LICENSE` file in this repository.
**Use of this file is subject to the use restrictions in Section 3.2 of that
agreement.** If you distribute this file, or anything derived from it, you must
pass those restrictions on to whoever you distribute to, provide them a copy of
the agreement, and include the `NOTICE` file.
Gemma 2, Gemma 3 and EmbeddingGemma are covered by these terms. **Gemma 4 is
not** β€” it is released under Apache-2.0, under a separate licence at
[ai.google.dev/gemma/apache_2](https://ai.google.dev/gemma/apache_2).