static_embeddings 0.1.4 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +118 -0
- data/README.md +67 -25
- data/Rakefile +1 -1
- data/docs/ARCHITECTURE.md +54 -34
- data/docs/LIMITATIONS.md +12 -6
- data/docs/MODEL_AUDIT.md +195 -52
- data/ext/static_embeddings/se_embed.c +2 -1
- data/ext/static_embeddings/se_format.c +41 -4
- data/ext/static_embeddings/se_internal.h +17 -5
- data/ext/static_embeddings/se_tokenizer.c +155 -18
- data/ext/static_embeddings/se_unicode.c +1 -1
- data/ext/static_embeddings/static_embeddings.c +32 -2
- data/lib/models/demo.semb +0 -0
- data/lib/static_embeddings/bert_wordpiece.rb +191 -0
- data/lib/static_embeddings/canonical.rb +50 -0
- data/lib/static_embeddings/cli.rb +88 -62
- data/lib/static_embeddings/codec.rb +45 -0
- data/lib/static_embeddings/conversion.rb +58 -0
- data/lib/static_embeddings/errors.rb +2 -2
- data/lib/static_embeddings/format/constants.rb +109 -0
- data/lib/static_embeddings/format/hash_table.rb +69 -0
- data/lib/static_embeddings/format/trie.rb +78 -0
- data/lib/static_embeddings/format/verifier.rb +41 -0
- data/lib/static_embeddings/format/writer.rb +131 -0
- data/lib/static_embeddings/format.rb +3 -300
- data/lib/static_embeddings/importers/model2vec.rb +52 -0
- data/lib/static_embeddings/importers/sentence_transformers_static.rb +103 -0
- data/lib/static_embeddings/importers/support.rb +111 -0
- data/lib/static_embeddings/importers.rb +50 -0
- data/lib/static_embeddings/model.rb +35 -20
- data/lib/static_embeddings/paths.rb +17 -4
- data/lib/static_embeddings/provenance.rb +58 -0
- data/lib/static_embeddings/reference.rb +90 -33
- data/lib/static_embeddings/row_prefix_payload.rb +59 -0
- data/lib/static_embeddings/safetensors.rb +178 -34
- data/lib/static_embeddings/version.rb +1 -1
- data/lib/static_embeddings.rb +29 -57
- data/static_embeddings.gemspec +2 -2
- data/tools/check_model2vec_parity.rb +89 -54
- data/tools/check_st_parity.rb +125 -0
- data/tools/eval_retrieval.rb +58 -0
- metadata +24 -6
- data/lib/static_embeddings/converter.rb +0 -284
data/docs/MODEL_AUDIT.md
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
# Model Audit
|
|
2
2
|
|
|
3
|
-
A converted model is trusted only after
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
A converted model is trusted only after a recorded comparison against an
|
|
4
|
+
external upstream oracle. An audit record covers one source-model snapshot,
|
|
5
|
+
one `.semb` format/runtime contract, pinned upstream package versions, and the
|
|
6
|
+
specific corpus that was checked. A passing corpus is evidence for those rows;
|
|
7
|
+
it is **not** an exhaustive proof that every Unicode string is equivalent.
|
|
8
|
+
|
|
9
|
+
`StaticEmbeddings::Reference` remains useful for high-volume differential fuzz
|
|
10
|
+
of the C implementation, but it is an implementation twin and is not counted as
|
|
11
|
+
independent upstream evidence.
|
|
8
12
|
|
|
9
13
|
## potion-retrieval-32m
|
|
10
14
|
|
|
11
|
-
| runtime |
|
|
15
|
+
| runtime | external result | note |
|
|
12
16
|
|---|---|---|
|
|
13
|
-
| 0.1.1 |
|
|
14
|
-
| 0.1.2 |
|
|
15
|
-
| 0.1.3 |
|
|
16
|
-
| 0.1.4 |
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
matches HuggingFace, because it is an implementation twin of the C runtime
|
|
21
|
-
written in this repository. The 0.1.3 release candidate was checked against a
|
|
22
|
-
fresh upstream `model2vec.StaticModel` oracle that includes DEL, control
|
|
23
|
-
characters, Unicode, OOV, long-word and truncation rows. 0.1.4 re-ran that
|
|
24
|
-
oracle against the SIMD L2 runtime on the same snapshot and `.semb`.
|
|
17
|
+
| 0.1.1 | 31-row corpus passed | superseded |
|
|
18
|
+
| 0.1.2 | not re-run | superseded before release |
|
|
19
|
+
| 0.1.3 | 31-row corpus passed | superseded |
|
|
20
|
+
| 0.1.4 | 31-row corpus passed | historical result below; later review found uncovered boundary cases |
|
|
21
|
+
| 0.1.5 | **434-row corpus passed** | format v3; CI `potion_audit` record below |
|
|
22
|
+
|
|
23
|
+
### Historical 0.1.4 record
|
|
25
24
|
|
|
26
25
|
Source model:
|
|
27
26
|
|
|
@@ -29,7 +28,6 @@ Source model:
|
|
|
29
28
|
- Hugging Face snapshot: `6fc8051fab2a1e0ee76689cf08c853792ac285e7`
|
|
30
29
|
- Oracle implementation: `model2vec.StaticModel.from_pretrained`
|
|
31
30
|
- Python package: `model2vec 0.9.0` (`tokenizers 0.23.1`, `numpy 2.5.2`)
|
|
32
|
-
- Oracle file: `tmp/model2vec_oracle.json`
|
|
33
31
|
- Oracle rows in this recorded run: `31`
|
|
34
32
|
- Oracle dimension: `512`
|
|
35
33
|
- Oracle max length: `512`
|
|
@@ -37,25 +35,12 @@ Source model:
|
|
|
37
35
|
|
|
38
36
|
Converted `.semb`:
|
|
39
37
|
|
|
40
|
-
- Path: `$HOME/.cache/static_embeddings/models/potion-retrieval-32m.semb`
|
|
41
38
|
- Format version: `2`
|
|
42
39
|
- Header size: `320`
|
|
43
40
|
- Bytes: `135411608`
|
|
44
41
|
- SHA256: `79e087863d2bab825779fd7de3574e5625542ef6a5ecad33fe681ea16d4b3ab0`
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
python tools/model2vec_oracle.py \
|
|
50
|
-
~/.cache/huggingface/hub/models--minishlab--potion-retrieval-32M/snapshots/6fc8051fab2a1e0ee76689cf08c853792ac285e7 \
|
|
51
|
-
--out tmp/model2vec_oracle.json
|
|
52
|
-
|
|
53
|
-
bundle exec rake parity \
|
|
54
|
-
MODEL="$HOME/.cache/static_embeddings/models/potion-retrieval-32m.semb" \
|
|
55
|
-
ORACLE=tmp/model2vec_oracle.json
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Parity result (0.1.4, 2026-08-31):
|
|
43
|
+
Recorded result (2026-08-31):
|
|
59
44
|
|
|
60
45
|
```text
|
|
61
46
|
rows=31
|
|
@@ -67,22 +52,180 @@ vector_failures=[]
|
|
|
67
52
|
parity OK
|
|
68
53
|
```
|
|
69
54
|
|
|
70
|
-
The
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
55
|
+
The last line means **31/31 rows in that historical oracle passed**. It must not
|
|
56
|
+
be read as a global tokenizer-equivalence claim. The corpus did not cover, for
|
|
57
|
+
example, the corrected UNK-before-truncation ordering, standard AddedVocabulary
|
|
58
|
+
literals, or the Rust/Python CJK Extension E boundary.
|
|
59
|
+
|
|
60
|
+
## 0.1.5 record
|
|
61
|
+
|
|
62
|
+
0.1.5 separates three contracts that the old oracle mixed together:
|
|
63
|
+
|
|
64
|
+
1. raw Hugging Face `tokenizers 0.23.1` ids, including `[UNK]`;
|
|
65
|
+
2. this runtime's usable-id embedding contract (`[UNK]` drop, then token cap);
|
|
66
|
+
3. `model2vec.StaticModel` vectors where its character pre-cut does not change
|
|
67
|
+
the usable token sequence.
|
|
68
|
+
|
|
69
|
+
Rows changed solely by Model2Vec's `max_length * median_token_length` character
|
|
70
|
+
pre-cut are reported as intentional deviations rather than hidden inside a pass.
|
|
71
|
+
A passing corpus is evidence for those 434 rows, not an exhaustive proof of
|
|
72
|
+
every Unicode string.
|
|
73
|
+
|
|
74
|
+
Recorded from GitHub Actions (`potion_audit`, Python 3.12.14, pinned
|
|
75
|
+
`model2vec==0.9.0` / `tokenizers==0.23.1` / `numpy==2.5.2`):
|
|
76
|
+
|
|
77
|
+
Source model:
|
|
78
|
+
|
|
79
|
+
- Hugging Face repository: `minishlab/potion-retrieval-32M`
|
|
80
|
+
- Hugging Face snapshot: `6fc8051fab2a1e0ee76689cf08c853792ac285e7`
|
|
81
|
+
- Oracle implementation: `model2vec.StaticModel.from_pretrained`
|
|
82
|
+
- Oracle rows: `434` from `tools/parity_cases.py`
|
|
83
|
+
- Oracle dimension: `512`
|
|
84
|
+
- Oracle max length: `512`
|
|
85
|
+
- Runtime at time of this record: `static_embeddings 0.1.5`
|
|
86
|
+
|
|
87
|
+
Converted `.semb` (CI artifact; Unicode tables stamped from the Ubuntu Ruby that
|
|
88
|
+
converted it, so the SHA is not expected to match a macOS local convert of the
|
|
89
|
+
same snapshot):
|
|
90
|
+
|
|
91
|
+
- Format version: `3`
|
|
92
|
+
- Bytes: `135411800`
|
|
93
|
+
- SHA256: `747231b5afbcb3b16bf2b04538f81d3a96be88a798982214b7cf01eddbdcf4eb`
|
|
94
|
+
- `dim=512` `vocab=63091`
|
|
95
|
+
|
|
96
|
+
Recorded result:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
rows=434
|
|
100
|
+
vectors_checked=432
|
|
101
|
+
intentional_character_pretruncate_deviations=2
|
|
102
|
+
min_cosine=0.9999999999999989
|
|
103
|
+
max_abs_all=1.4901161193847656e-08
|
|
104
|
+
raw_token_id_failures=[]
|
|
105
|
+
usable_token_id_failures=[]
|
|
106
|
+
embed_invariant_failures=[]
|
|
107
|
+
vector_failures=[]
|
|
108
|
+
corpus parity OK (434/434); intentional Model2Vec character pre-truncation deviations are reported separately
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The two intentional deviations are `long:sparse-whitespace` and
|
|
112
|
+
`long:unknown-prefix`. Vectors were not required to match Model2Vec on those
|
|
113
|
+
rows. The remaining 432 rows were inside `cosine >= 1 - 1e-6` and
|
|
114
|
+
`max_abs <= 1e-5`.
|
|
115
|
+
|
|
116
|
+
The preceding CI job `upstream_parity` ran the same 434-row corpus against the
|
|
117
|
+
synthetic `tiny-wordpiece` fixture: `434/434`, all four failure lists empty,
|
|
118
|
+
`vectors_checked=429`, `intentional_character_pretruncate_deviations=5`,
|
|
119
|
+
`min_cosine=0.9999999999999988`, `max_abs_all=5.960464477539063e-08`. That job
|
|
120
|
+
proves the fixture loads in `StaticModel.from_pretrained` and that the checker
|
|
121
|
+
contracts hold; it is not a potion audit.
|
|
122
|
+
|
|
123
|
+
## 1.5.6 Patch 1 proof
|
|
124
|
+
|
|
125
|
+
Architecture: Model2Vec / Sentence Transformers import functions → immutable canonical data →
|
|
126
|
+
existing WordPiece `.semb` v3 writer. C runtime unchanged.
|
|
127
|
+
|
|
128
|
+
### potion-retrieval-32M reconvert
|
|
129
|
+
|
|
130
|
+
Official snapshot `6fc8051fab2a1e0ee76689cf08c853792ac285e7` ships a Sentence
|
|
131
|
+
Transformers `modules.json` (`StaticEmbedding` + `Normalize`). Detection uses
|
|
132
|
+
`config.json` `model_type=model2vec` first, so this stays UNK_DROP / L2 / 512.
|
|
133
|
+
|
|
134
|
+
New file `potion-retrieval-32m-reconvert.semb` vs existing
|
|
135
|
+
`potion-retrieval-32m-v3.semb`: vocab/hash/embeddings/norm_tables/tries
|
|
136
|
+
byte-identical. 10-text corpus `max_abs=0`. Provenance JSON differs (new keys).
|
|
137
|
+
|
|
138
|
+
### static-retrieval-mrl-en-v1 vs SentenceTransformer.encode
|
|
139
|
+
|
|
140
|
+
Oracle: `tools/st_oracle.py` + `tools/check_st_parity.rb`,
|
|
141
|
+
`sentence-transformers 6.0.1`, `add_special_tokens=false`,
|
|
142
|
+
`normalize_embeddings=false`, 436 rows including `all-unk:private-use` and
|
|
143
|
+
`long:known-words-600`.
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
1024: 436/436 min_cosine=0.999999999999999 max_abs_all=7.62939453125e-06
|
|
147
|
+
512: 436/436 min_cosine=0.9999999999999989 max_abs_all=7.62939453125e-06
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
512 compared as prefix of the 1024-d encode vectors. Ruby Reference matched C
|
|
151
|
+
separately. Diagnostic `StaticEmbedding.forward` matched encode on this model.
|
|
152
|
+
|
|
153
|
+
### UNK and max_tokens on the real models
|
|
154
|
+
|
|
155
|
+
- potion-retrieval-32M `🧬 🧬 🧬` → UNK ids dropped → zero vector; 600×`hello`
|
|
156
|
+
→ `truncated=true pooled=512`.
|
|
157
|
+
- static-retrieval-mrl-en-v1-1024 same emoji string → three UNK ids pooled
|
|
158
|
+
(non-zero, mean of UNK rows); 600×`hello` → `truncated=false pooled=600`.
|
|
159
|
+
- Private-use codepoints are BertNormalizer `Co` and become empty, not UNK.
|
|
160
|
+
|
|
161
|
+
## 1.5.6 Type A conversions
|
|
162
|
+
|
|
163
|
+
These records prove the offline import layer maps extra WordPiece sources onto
|
|
164
|
+
the existing runtime. They are **not** a replacement for the
|
|
165
|
+
potion-retrieval-32m Python oracle above. `static-retrieval-mrl-en-v1` has the
|
|
166
|
+
separate `SentenceTransformer.encode` oracle recorded above; the multilingual
|
|
167
|
+
similarity artifact was checked against the Ruby `.semb` Reference twin and the
|
|
168
|
+
small Russian retrieval fixture, not claimed as a general upstream retrieval
|
|
169
|
+
benchmark.
|
|
170
|
+
|
|
171
|
+
All conversions used `static_embeddings 1.5.6` on arm64-darwin24.
|
|
172
|
+
|
|
173
|
+
### minishlab/potion-base-8M
|
|
174
|
+
|
|
175
|
+
- Snapshot: `bf8b056651a2c21b8d2565580b8569da283cab23`
|
|
176
|
+
- Layout: Model2Vec (`UNK_DROP`, L2, `max_tokens` 512)
|
|
177
|
+
- `.semb`: `potion-base-8m.semb` `dim=256` `vocab=29528` `33346200` bytes
|
|
178
|
+
SHA256 `8abd8d1f26511959e14ca26ec4dcacad1d3db9b4b66b2e67683fb2d06074f490`
|
|
179
|
+
- Native C matched Ruby Reference on short English/OOV/empty texts (`max_abs < 1e-5`)
|
|
180
|
+
- `model2vec 0.9.0` / `tokenizers 0.23.1` oracle: 434/434, vectors_checked=432,
|
|
181
|
+
intentional character-pretruncate deviations=2, min_cosine=0.9999999999999989,
|
|
182
|
+
max_abs_all=2.98e-08
|
|
183
|
+
|
|
184
|
+
### minishlab/potion-science-32M
|
|
185
|
+
|
|
186
|
+
- Snapshot: `7366079845507de14a4330007cdfa01bb92bca52`
|
|
187
|
+
- Layout: Model2Vec (`UNK_DROP`, L2, `max_tokens` 512)
|
|
188
|
+
- `.semb`: `potion-science-32m.semb` `dim=256` `vocab=124428` `140353240` bytes
|
|
189
|
+
SHA256 `e968f93b89d59d9c63c6a8152495283d1138144e61ed5c08d306c21c00b7cdb1`
|
|
190
|
+
- Native C matched Ruby Reference on English and Russian snippets (`max_abs < 1e-5`)
|
|
191
|
+
- `model2vec 0.9.0` / `tokenizers 0.23.1` oracle: 434/434, vectors_checked=432,
|
|
192
|
+
intentional character-pretruncate deviations=2, min_cosine=0.9999999999999986,
|
|
193
|
+
max_abs_all=2.98e-08
|
|
194
|
+
|
|
195
|
+
### sentence-transformers/static-retrieval-mrl-en-v1
|
|
196
|
+
|
|
197
|
+
- Snapshot: `f60985c706f192d45d218078e49e5a8b6f15283a`
|
|
198
|
+
- Layout: Sentence Transformers StaticEmbedding (`UNK_INCLUDE`, no L2, unlimited)
|
|
199
|
+
- `static-retrieval-mrl-en-v1-1024.semb` `dim=1024` `vocab=30522` `128186264` bytes
|
|
200
|
+
SHA256 `f316bb07348503418ed0f6d897ddbf7b44422d2077ca4860518f31f328610946`
|
|
201
|
+
- `static-retrieval-mrl-en-v1-512.semb` `--dimensions 512` `65677208` bytes
|
|
202
|
+
SHA256 `efbb43c14def793d0bbb42ef591a0951caed23a49cdd94dcf72312569d1b7cff`
|
|
203
|
+
- Prefix of a 1024-d vector matched the 512-d artifact on in-vocabulary English
|
|
204
|
+
(valid because this source does not L2-normalize)
|
|
205
|
+
- Native C matched Ruby Reference on short texts
|
|
206
|
+
|
|
207
|
+
### sentence-transformers/static-similarity-mrl-multilingual-v1
|
|
208
|
+
|
|
209
|
+
- Snapshot: `b68f4122911bcffcd6e1f695f2d99cd6788972d8`
|
|
210
|
+
- Layout: Sentence Transformers StaticEmbedding (`UNK_INCLUDE`, no L2, unlimited)
|
|
211
|
+
- `static-similarity-mrl-multilingual-v1-512.semb` `--dimensions 512` `vocab=105879`
|
|
212
|
+
`227983824` bytes SHA256 `8d9a63d20ee23468ae2fd2584ef3f0ec75e720e68410e416157aaf2b873e110e`
|
|
213
|
+
- `static-similarity-mrl-multilingual-v1-256.semb` `--dimensions 256` `119563728` bytes
|
|
214
|
+
SHA256 `2e9e689bd3afd18ed50a77f0f8842b4b5a5be39bfad75e3fa24ad99b478de068`
|
|
215
|
+
- Native C matched Ruby Reference on Russian text (`max_abs 9.1e-7`)
|
|
216
|
+
|
|
217
|
+
This model is published as similarity, not retrieval. `tools/eval_retrieval.rb`
|
|
218
|
+
on `test/fixtures/russian_faq_eval.json` (10 queries / 10 docs, cosine@10,
|
|
219
|
+
arm64-darwin24, 1.5.6):
|
|
220
|
+
|
|
221
|
+
| model | dim | MRR | nDCG@10 |
|
|
222
|
+
|---|---:|---:|---:|
|
|
223
|
+
| static-similarity-mrl-multilingual-v1-256 | 256 | 0.875 | 0.906 |
|
|
224
|
+
| static-similarity-mrl-multilingual-v1-512 | 512 | 0.850 | 0.889 |
|
|
225
|
+
| static-retrieval-mrl-en-v1-512 | 512 | 0.792 | 0.842 |
|
|
226
|
+
| potion-retrieval-32m | 512 | 0.733 | 0.799 |
|
|
227
|
+
| potion-base-8m | 256 | 0.712 | 0.780 |
|
|
228
|
+
|
|
229
|
+
Hit@10 was 1.0 for every model on this tiny labeled set. That is a domain sanity
|
|
230
|
+
check, not a public retrieval benchmark, and it does not make the similarity
|
|
231
|
+
model a hard-coded Russian default.
|
|
@@ -197,7 +197,8 @@ se_status_t se_embed_ids(const se_model_t *model, se_scratch_t *sc, const uint32
|
|
|
197
197
|
se_status_t se_embed_one(const se_model_t *model, se_scratch_t *sc, const uint8_t *input,
|
|
198
198
|
size_t input_len, uint32_t max_tokens, float *out, se_token_stats_t *stats,
|
|
199
199
|
se_error_t *err, volatile sig_atomic_t *cancelled) {
|
|
200
|
-
se_status_t rc = se_tokenize(model, sc, input, input_len, max_tokens,
|
|
200
|
+
se_status_t rc = se_tokenize(model, sc, input, input_len, max_tokens, SE_TOKEN_LIMIT_USABLE,
|
|
201
|
+
stats, err, cancelled);
|
|
201
202
|
if (rc != SE_OK)
|
|
202
203
|
return rc;
|
|
203
204
|
return embed_ids_core(model, sc, sc->ids, stats->token_count, out, err, cancelled);
|
|
@@ -102,7 +102,7 @@ int se_vocab_lookup_piece(const se_model_t *model, const uint8_t *prefix, size_t
|
|
|
102
102
|
uint32_t pos = h & mask;
|
|
103
103
|
uint32_t total_len = (uint32_t)(prefix_len + len);
|
|
104
104
|
|
|
105
|
-
for (uint32_t probe = 0; probe
|
|
105
|
+
for (uint32_t probe = 0; probe < model->meta.max_probe; probe++) {
|
|
106
106
|
const se_vocab_slot_t *slot = &model->vocab_hash[pos];
|
|
107
107
|
if (slot->token_id == SE_SLOT_EMPTY)
|
|
108
108
|
return 0;
|
|
@@ -556,6 +556,31 @@ static se_status_t validate_vocab_hash(const se_model_t *model, struct section v
|
|
|
556
556
|
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
557
557
|
"[UNK] is not reachable through the vocabulary hash");
|
|
558
558
|
rc = SE_ERR_INVALID_FORMAT;
|
|
559
|
+
goto done;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
static const struct {
|
|
563
|
+
uint32_t bit;
|
|
564
|
+
const char *text;
|
|
565
|
+
uint32_t len;
|
|
566
|
+
} added[] = {
|
|
567
|
+
{SE_ADDED_PAD, "[PAD]", 5u}, {SE_ADDED_UNK, "[UNK]", 5u}, {SE_ADDED_CLS, "[CLS]", 5u},
|
|
568
|
+
{SE_ADDED_SEP, "[SEP]", 5u}, {SE_ADDED_MASK, "[MASK]", 6u},
|
|
569
|
+
};
|
|
570
|
+
const uint32_t ids[] = {m->pad_id, m->unk_id, m->cls_id, m->sep_id, m->mask_id};
|
|
571
|
+
|
|
572
|
+
for (size_t i = 0; i < SE_ARRAY_LEN(added); i++) {
|
|
573
|
+
if ((m->added_token_mask & added[i].bit) == 0)
|
|
574
|
+
continue;
|
|
575
|
+
uint32_t got = 0;
|
|
576
|
+
if (ids[i] >= m->vocab_size ||
|
|
577
|
+
!se_vocab_lookup(model, (const uint8_t *)added[i].text, added[i].len, &got) ||
|
|
578
|
+
got != ids[i]) {
|
|
579
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
580
|
+
"added token %s is not reachable at its recorded id", added[i].text);
|
|
581
|
+
rc = SE_ERR_INVALID_FORMAT;
|
|
582
|
+
goto done;
|
|
583
|
+
}
|
|
559
584
|
}
|
|
560
585
|
|
|
561
586
|
done:
|
|
@@ -646,6 +671,7 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
646
671
|
m->subword_prefix_len = read_u32(base, SE_OFF_SUBWORD_PREFIX_LEN);
|
|
647
672
|
m->max_token_chars = read_u32(base, SE_OFF_MAX_TOKEN_CHARS);
|
|
648
673
|
m->max_probe = read_u32(base, SE_OFF_MAX_PROBE);
|
|
674
|
+
m->added_token_mask = read_u32(base, SE_OFF_ADDED_TOKEN_MASK);
|
|
649
675
|
memcpy(m->subword_prefix, base + SE_OFF_SUBWORD_PREFIX, 8);
|
|
650
676
|
|
|
651
677
|
if (m->tokenizer_type != SE_TOKENIZER_BERT_WORDPIECE_V1) {
|
|
@@ -669,7 +695,7 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
669
695
|
m->normalization_type);
|
|
670
696
|
return SE_ERR_INVALID_FORMAT;
|
|
671
697
|
}
|
|
672
|
-
if (m->truncation_policy !=
|
|
698
|
+
if (m->truncation_policy != SE_TRUNCATE_USABLE_IDS_BEFORE_POOLING) {
|
|
673
699
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "truncation policy %u is not supported",
|
|
674
700
|
m->truncation_policy);
|
|
675
701
|
return SE_ERR_INVALID_FORMAT;
|
|
@@ -682,6 +708,11 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
682
708
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown UNK policy %u", m->unk_policy);
|
|
683
709
|
return SE_ERR_INVALID_FORMAT;
|
|
684
710
|
}
|
|
711
|
+
if ((m->added_token_mask & ~SE_ADDED_TOKEN_MASK_ALL) != 0) {
|
|
712
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown added-token mask bits 0x%x",
|
|
713
|
+
m->added_token_mask & ~SE_ADDED_TOKEN_MASK_ALL);
|
|
714
|
+
return SE_ERR_INVALID_FORMAT;
|
|
715
|
+
}
|
|
685
716
|
if (m->empty_policy != SE_EMPTY_ZERO_VECTOR && m->empty_policy != SE_EMPTY_RAISE) {
|
|
686
717
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown empty-input policy %u", m->empty_policy);
|
|
687
718
|
return SE_ERR_INVALID_FORMAT;
|
|
@@ -704,8 +735,14 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
704
735
|
m->hash_table_size);
|
|
705
736
|
return SE_ERR_INVALID_FORMAT;
|
|
706
737
|
}
|
|
707
|
-
if (m->hash_table_size
|
|
708
|
-
|
|
738
|
+
if (m->hash_table_size <= m->vocab_size ||
|
|
739
|
+
(uint64_t)m->vocab_size * 100u > (uint64_t)m->hash_table_size * 70u) {
|
|
740
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
741
|
+
"hash table load factor exceeds the supported 0.70 maximum");
|
|
742
|
+
return SE_ERR_INVALID_FORMAT;
|
|
743
|
+
}
|
|
744
|
+
if (m->max_probe == 0 || m->max_probe > m->hash_table_size) {
|
|
745
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "invalid recorded max probe %u", m->max_probe);
|
|
709
746
|
return SE_ERR_INVALID_FORMAT;
|
|
710
747
|
}
|
|
711
748
|
if (m->subword_prefix_len > 8) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#define SE_MAGIC "SEMBv1\0\0"
|
|
10
10
|
#define SE_MAGIC_LEN 8
|
|
11
|
-
#define SE_FORMAT_VERSION
|
|
11
|
+
#define SE_FORMAT_VERSION 3u
|
|
12
12
|
#define SE_HEADER_SIZE 320u
|
|
13
13
|
#define SE_ALIGNMENT 64u
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#define SE_NORMALIZATION_NONE 0u
|
|
19
19
|
#define SE_NORMALIZATION_L2 1u
|
|
20
20
|
|
|
21
|
-
#define
|
|
21
|
+
#define SE_TRUNCATE_USABLE_IDS_BEFORE_POOLING 2u
|
|
22
22
|
|
|
23
23
|
#define SE_UNK_INCLUDE 0u
|
|
24
24
|
#define SE_UNK_DROP 1u
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
#define SE_EMPTY_ZERO_VECTOR 0u
|
|
27
27
|
#define SE_EMPTY_RAISE 1u
|
|
28
28
|
|
|
29
|
+
#define SE_ADDED_PAD (1u << 0)
|
|
30
|
+
#define SE_ADDED_UNK (1u << 1)
|
|
31
|
+
#define SE_ADDED_CLS (1u << 2)
|
|
32
|
+
#define SE_ADDED_SEP (1u << 3)
|
|
33
|
+
#define SE_ADDED_MASK (1u << 4)
|
|
34
|
+
#define SE_ADDED_TOKEN_MASK_ALL \
|
|
35
|
+
(SE_ADDED_PAD | SE_ADDED_UNK | SE_ADDED_CLS | SE_ADDED_SEP | SE_ADDED_MASK)
|
|
36
|
+
|
|
29
37
|
#define SE_SLOT_EMPTY 0xFFFFFFFFu
|
|
30
38
|
#define SE_SIZE_MAX ((size_t)-1)
|
|
31
39
|
|
|
@@ -83,7 +91,8 @@ enum {
|
|
|
83
91
|
SE_OFF_SEC_ROOT_TRIE = 208,
|
|
84
92
|
SE_OFF_SEC_CONT_TRIE = 224,
|
|
85
93
|
SE_OFF_CHECKSUM = 240,
|
|
86
|
-
SE_OFF_MAX_PROBE = 304
|
|
94
|
+
SE_OFF_MAX_PROBE = 304,
|
|
95
|
+
SE_OFF_ADDED_TOKEN_MASK = 308
|
|
87
96
|
};
|
|
88
97
|
|
|
89
98
|
typedef struct {
|
|
@@ -163,6 +172,7 @@ typedef struct {
|
|
|
163
172
|
uint32_t max_input_chars_per_word;
|
|
164
173
|
uint32_t max_token_chars;
|
|
165
174
|
uint32_t max_probe;
|
|
175
|
+
uint32_t added_token_mask;
|
|
166
176
|
uint32_t pad_id;
|
|
167
177
|
uint32_t unk_id;
|
|
168
178
|
uint32_t cls_id;
|
|
@@ -401,9 +411,11 @@ typedef struct {
|
|
|
401
411
|
uint32_t truncated;
|
|
402
412
|
} se_token_stats_t;
|
|
403
413
|
|
|
414
|
+
typedef enum { SE_TOKEN_LIMIT_RAW = 0, SE_TOKEN_LIMIT_USABLE = 1 } se_token_limit_t;
|
|
415
|
+
|
|
404
416
|
se_status_t se_tokenize(const se_model_t *model, se_scratch_t *scratch, const uint8_t *input,
|
|
405
|
-
size_t input_len, uint32_t max_tokens,
|
|
406
|
-
se_error_t *err, volatile sig_atomic_t *cancelled);
|
|
417
|
+
size_t input_len, uint32_t max_tokens, se_token_limit_t limit_mode,
|
|
418
|
+
se_token_stats_t *stats, se_error_t *err, volatile sig_atomic_t *cancelled);
|
|
407
419
|
|
|
408
420
|
se_status_t se_embed_one(const se_model_t *model, se_scratch_t *scratch, const uint8_t *input,
|
|
409
421
|
size_t input_len, uint32_t max_tokens, float *out, se_token_stats_t *stats,
|