static_embeddings 0.1.5 → 1.5.7
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 +60 -0
- data/README.md +17 -2
- data/docs/ARCHITECTURE.md +14 -5
- data/docs/MODEL_AUDIT.md +110 -0
- data/ext/static_embeddings/se_alloc_stats.c +5 -6
- data/ext/static_embeddings/se_f16.c +1 -1
- data/ext/static_embeddings/se_tokenizer.c +6 -0
- data/ext/static_embeddings/se_topk.c +1 -2
- data/ext/static_embeddings/static_embeddings.c +60 -10
- 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 -63
- 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 -338
- 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 +8 -12
- data/lib/static_embeddings/provenance.rb +58 -0
- data/lib/static_embeddings/reference.rb +50 -40
- data/lib/static_embeddings/row_prefix_payload.rb +59 -0
- data/lib/static_embeddings/version.rb +1 -1
- data/lib/static_embeddings.rb +29 -55
- data/static_embeddings.gemspec +2 -2
- data/tools/check_model2vec_parity.rb +5 -1
- 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 -328
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8e174dd93ba3b3e9ee25047fd28ea5ba980d096a4922c0b8325c366208bff65
|
|
4
|
+
data.tar.gz: 741e0cc35791d9a335c82a05aed18e3388232828824168ff59ed1751c281e8a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58462ed4c203fb657630d092e1a4aa85857e5d44106bb97feae26f12579fc4b6d88d20632e462624ad0cb93e1d0d0e735f91ee12aaec80b637d29b313e6dc53d
|
|
7
|
+
data.tar.gz: 80e9897969dfe6aace3d851f1a11ffb6610b628cddc35239301a676b2ce32d97d1a8a614ae6dca0526703e61d6d813af89965d0906c593392e66cdd25f7e16da
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.7
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Model mappings are no longer closed while embedding, warmup, or token-ID
|
|
8
|
+
operations are using them without the GVL. `Model#close` raises
|
|
9
|
+
`StaticEmbeddings::Error` while such an operation is active.
|
|
10
|
+
- Tokenizer code-point writes now verify the capacity invariant after growth.
|
|
11
|
+
- Resolved Clang conversion warnings and formatting issues in the native code.
|
|
12
|
+
|
|
13
|
+
## 1.5.6
|
|
14
|
+
|
|
15
|
+
Multi-source WordPiece release. The native `.semb` v3 runtime is unchanged; new
|
|
16
|
+
source families are normalized offline into the same audited
|
|
17
|
+
`BERT_WORDPIECE_V1 -> lookup -> mean -> NONE/L2` contract. Model identity is
|
|
18
|
+
chosen at `load_model`, not per `embed` call.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Model2Vec and Sentence Transformers importers.** A Model2Vec directory and a
|
|
23
|
+
Sentence Transformers `StaticEmbedding` directory (`modules.json` + exactly
|
|
24
|
+
one module path) compile to the same canonical data. The ST importer is
|
|
25
|
+
fail-closed and confines the module path to the source root with `realpath`.
|
|
26
|
+
- **Source-faithful policies.** Model2Vec uses `UNK_DROP`, normalization from its
|
|
27
|
+
config and a 512-token default. Sentence Transformers `StaticEmbedding` uses
|
|
28
|
+
`UNK_INCLUDE`, `NORMALIZATION_NONE`, unlimited tokens and
|
|
29
|
+
`add_special_tokens=false`.
|
|
30
|
+
- **`--dimensions N`.** Matryoshka prefix slicing happens while streaming the
|
|
31
|
+
embedding matrix; the runtime still loads an ordinary fixed-dimension `.semb`.
|
|
32
|
+
- **`--max-tokens unlimited`.** Convert-time `0` remains an alias; runtime
|
|
33
|
+
`max_tokens: 0` is still rejected and `false` means unlimited.
|
|
34
|
+
- **Russian retrieval fixture.** `tools/eval_retrieval.rb` and the checked-in
|
|
35
|
+
Russian FAQ corpus provide a small domain sanity check for the multilingual
|
|
36
|
+
WordPiece model; it is not presented as a general benchmark.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Offline Ruby conversion was simplified.** Stateful `Converter` and the
|
|
41
|
+
behavior-heavy `CanonicalModel` were replaced by import functions, immutable
|
|
42
|
+
canonical data, pure provenance/meta transforms and a streaming format writer.
|
|
43
|
+
Writer/hash/trie/verifier code is split from runtime format constants, so plain
|
|
44
|
+
`require "static_embeddings"` does not load conversion code.
|
|
45
|
+
- The Ruby `Reference` twin follows canonical UNK/normalization/max-token
|
|
46
|
+
policies instead of re-deriving Model2Vec defaults.
|
|
47
|
+
- Model2Vec `normalize` now defaults to `false` when the key is absent, matching
|
|
48
|
+
the pinned upstream behavior. Unknown source families are rejected rather than
|
|
49
|
+
guessed from `tokenizer.json + model.safetensors`.
|
|
50
|
+
- Header integer writes validate their unsigned range instead of silently
|
|
51
|
+
wrapping oversized Ruby integers.
|
|
52
|
+
- `Model#provenance` is parsed once and cached.
|
|
53
|
+
|
|
54
|
+
Local WordPiece conversions requiring **no C change**: `potion-base-8M`,
|
|
55
|
+
`potion-science-32M`, `static-retrieval-mrl-en-v1` at 1024/512 and
|
|
56
|
+
`static-similarity-mrl-multilingual-v1` at 512/256. The retrieval model has a
|
|
57
|
+
recorded `SentenceTransformer.encode` oracle (436/436). Records are in
|
|
58
|
+
`docs/MODEL_AUDIT.md`.
|
|
59
|
+
|
|
60
|
+
SentencePiece/Unigram is intentionally **not** part of 1.5.6; it remains a
|
|
61
|
+
future runtime capability rather than shipping an unaudited v4 path.
|
|
62
|
+
|
|
3
63
|
## 0.1.5
|
|
4
64
|
|
|
5
65
|
Correctness/parity release. The native runtime architecture is unchanged, but
|
data/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# static_embeddings
|
|
2
2
|
|
|
3
|
-
A Ruby runtime for converted Model2Vec
|
|
3
|
+
A Ruby runtime for converted Model2Vec and Sentence Transformers static embedding models. A model
|
|
4
4
|
is converted once into a local `.semb` file and loaded through a small C
|
|
5
5
|
extension. No ONNX Runtime, no Rust or Python at runtime, no network access, one
|
|
6
6
|
mmap-able file, binary float32 output.
|
|
7
7
|
|
|
8
|
-
The
|
|
8
|
+
The reference production target remains `minishlab/potion-retrieval-32M`; 1.5.6 also supports audited WordPiece `StaticEmbedding` sources.
|
|
9
9
|
|
|
10
10
|
```ruby
|
|
11
11
|
require "static_embeddings"
|
|
@@ -49,6 +49,21 @@ bundle exec ruby -Ilib exe/static_embeddings convert ./potion-retrieval-32M \
|
|
|
49
49
|
--id potion-retrieval-32m
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
Sentence Transformers static models (`modules.json` + one `StaticEmbedding`
|
|
53
|
+
module) use the same command. Matryoshka prefix-slice is a convert-time flag,
|
|
54
|
+
not a runtime `dim:`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec ruby -Ilib exe/static_embeddings convert ./static-retrieval-mrl-en-v1 \
|
|
58
|
+
--id static-retrieval-mrl-en-v1-512 \
|
|
59
|
+
--dimensions 512 \
|
|
60
|
+
--trained-mrl-dims 1024,512,256,128,64,32
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`--max-tokens unlimited` bakes no token cap (Sentence Transformers default).
|
|
64
|
+
`--max-tokens 0` is a convert-time alias; `embed(max_tokens: 0)` is still
|
|
65
|
+
rejected. Query and documents must use the same loaded `.semb`.
|
|
66
|
+
|
|
52
67
|
The result lands in `~/.cache/static_embeddings/models/potion-retrieval-32m.semb`.
|
|
53
68
|
Inspect or verify it with the `inspect` and `verify` subcommands, then load it:
|
|
54
69
|
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -8,10 +8,10 @@ HuggingFace / Model2Vec files offline, once, on your machine
|
|
|
8
8
|
model.safetensors
|
|
9
9
|
|
|
|
10
10
|
v
|
|
11
|
-
|
|
12
|
-
|
|
|
11
|
+
import -> canonical data -> writer <--- pure Ruby, offline, strict
|
|
12
|
+
| all source parsing/validation here
|
|
13
13
|
v
|
|
14
|
-
model.semb
|
|
14
|
+
model.semb <--- flat, versioned, mmap-able
|
|
15
15
|
|
|
|
16
16
|
v
|
|
17
17
|
C runtime <--- mmap + bounds checks + tokenize +
|
|
@@ -24,6 +24,15 @@ Everything expensive, fragile or security-sensitive about reading third-party
|
|
|
24
24
|
model files happens once, offline, in a language where it is easy to get
|
|
25
25
|
right. What remains in C is a bounds-checked mmap and three loops.
|
|
26
26
|
|
|
27
|
+
Only the offline import layer knows HuggingFace layouts. Model2Vec and Sentence
|
|
28
|
+
Transformers `StaticEmbedding` sources are reduced to immutable canonical data:
|
|
29
|
+
tokens, streamed matrix, tokenizer metadata, runtime policies and provenance.
|
|
30
|
+
Pure transforms then build the v3 header/provenance and the existing WordPiece
|
|
31
|
+
writer emits the artifact. A new WordPiece + mean-pooling source therefore does
|
|
32
|
+
not require a C change. New tokenizer families remain explicit future
|
|
33
|
+
capabilities and require a separately audited format/runtime change rather than
|
|
34
|
+
source-name special cases.
|
|
35
|
+
|
|
27
36
|
The C side is `se_format.c` (mmap and validation), `se_tokenizer.c`,
|
|
28
37
|
`se_embed.c`, `se_unicode.c`, `se_f16.c` (half-precision codec and kernels),
|
|
29
38
|
`se_topk.c`, `se_alloc_stats.c` (optional allocation counters), and
|
|
@@ -336,8 +345,8 @@ chunks -> .semb vectors --/
|
|
|
336
345
|
1. **Which potion models actually match `BERT_WORDPIECE_V1`.** The runtime now
|
|
337
346
|
implements the exact `normalized: false` extraction semantics needed by the
|
|
338
347
|
five standard BERT added tokens, but still rejects arbitrary AddedVocabulary,
|
|
339
|
-
BPE and Unigram profiles.
|
|
340
|
-
treated as compatible.
|
|
348
|
+
BPE and Unigram profiles. Type A (WordPiece + mean) models convert without a
|
|
349
|
+
C change; each source still needs an audit before it is treated as compatible.
|
|
341
350
|
2. **Russian.** Distilling a multilingual teacher into a WordPiece vocabulary
|
|
342
351
|
we control keeps the pure-C path, but skips the Tokenlearn pre-training
|
|
343
352
|
that gives the published potion models their quality. That gap has to be
|
data/docs/MODEL_AUDIT.md
CHANGED
|
@@ -119,3 +119,113 @@ synthetic `tiny-wordpiece` fixture: `434/434`, all four failure lists empty,
|
|
|
119
119
|
`min_cosine=0.9999999999999988`, `max_abs_all=5.960464477539063e-08`. That job
|
|
120
120
|
proves the fixture loads in `StaticModel.from_pretrained` and that the checker
|
|
121
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.
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
#include <string.h>
|
|
6
6
|
|
|
7
7
|
static const char *const se_alloc_category_names[SE_ALLOC_CATEGORY_COUNT] = {
|
|
8
|
-
"unknown",
|
|
9
|
-
"
|
|
10
|
-
"topk_best", "topk_matrix_copy", "format_validate", "model_file"};
|
|
8
|
+
"unknown", "scratch", "batch_input", "batch_output", "batch_stats", "batch_index",
|
|
9
|
+
"token_ids", "topk_query", "topk_best", "topk_matrix_copy", "format_validate", "model_file"};
|
|
11
10
|
|
|
12
11
|
const char *se_alloc_category_name(se_alloc_category_t category) {
|
|
13
12
|
if ((unsigned)category >= SE_ALLOC_CATEGORY_COUNT)
|
|
@@ -176,7 +175,8 @@ void *se_alloc_stats_realloc(se_alloc_category_t category, void *ptr, size_t byt
|
|
|
176
175
|
if (old_header->h.magic != SE_ALLOC_STATS_MAGIC)
|
|
177
176
|
abort();
|
|
178
177
|
|
|
179
|
-
se_alloc_category_t old_category =
|
|
178
|
+
se_alloc_category_t old_category =
|
|
179
|
+
normalize_category((se_alloc_category_t)old_header->h.category);
|
|
180
180
|
size_t old_bytes = old_header->h.size;
|
|
181
181
|
se_alloc_header_t *new_header = (se_alloc_header_t *)realloc(old_header, total);
|
|
182
182
|
if (!new_header)
|
|
@@ -223,8 +223,7 @@ void se_alloc_stats_snapshot(se_alloc_stats_t out[SE_ALLOC_CATEGORY_COUNT]) {
|
|
|
223
223
|
for (size_t i = 0; i < SE_ALLOC_CATEGORY_COUNT; i++) {
|
|
224
224
|
out[i].current_bytes = atomic_load_size(&se_alloc_stats[i].current_bytes);
|
|
225
225
|
out[i].peak_bytes = atomic_load_size(&se_alloc_stats[i].peak_bytes);
|
|
226
|
-
out[i].total_allocated_bytes =
|
|
227
|
-
atomic_load_size(&se_alloc_stats[i].total_allocated_bytes);
|
|
226
|
+
out[i].total_allocated_bytes = atomic_load_size(&se_alloc_stats[i].total_allocated_bytes);
|
|
228
227
|
out[i].total_freed_bytes = atomic_load_size(&se_alloc_stats[i].total_freed_bytes);
|
|
229
228
|
out[i].alloc_count = atomic_load_size(&se_alloc_stats[i].alloc_count);
|
|
230
229
|
out[i].realloc_count = atomic_load_size(&se_alloc_stats[i].realloc_count);
|
|
@@ -35,7 +35,7 @@ static void write_u16le(uint8_t *dst, uint16_t v) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
static uint16_t read_u16le(const uint8_t *src) {
|
|
38
|
-
return (uint16_t)src[0] | ((uint16_t)src[1] << 8);
|
|
38
|
+
return (uint16_t)((uint16_t)src[0] | ((uint16_t)src[1] << 8));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
uint16_t se_float_to_f16_bits(float value) {
|
|
@@ -512,6 +512,8 @@ static int append_segment_cp(token_state_t *st, uint32_t cp) {
|
|
|
512
512
|
st->segment_ascii = 0;
|
|
513
513
|
if (!grow_u32(&st->scratch->cps, &st->scratch->cps_cap, st->segment_len + 1))
|
|
514
514
|
return 0;
|
|
515
|
+
if (st->segment_len >= st->scratch->cps_cap)
|
|
516
|
+
return 0;
|
|
515
517
|
st->scratch->cps[st->segment_len++] = cp;
|
|
516
518
|
return 1;
|
|
517
519
|
}
|
|
@@ -906,6 +908,10 @@ static se_status_t tokenize_ascii_run(token_state_t *st, const uint8_t *input, s
|
|
|
906
908
|
return oom(st, "building token segment");
|
|
907
909
|
}
|
|
908
910
|
}
|
|
911
|
+
if (st->segment_len >= sc->cps_cap) {
|
|
912
|
+
*ip = i;
|
|
913
|
+
return oom(st, "building token segment");
|
|
914
|
+
}
|
|
909
915
|
sc->cps[st->segment_len++] =
|
|
910
916
|
(lc && b >= 'A' && b <= 'Z') ? (uint32_t)(b + 32u) : (uint32_t)b;
|
|
911
917
|
i++;
|
|
@@ -73,8 +73,7 @@ float se_dot_product_f32(const float *q, const float *row, size_t dim) {
|
|
|
73
73
|
return dot;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
float se_dot_and_row_sq_f32(const float *q, const float *row, size_t dim,
|
|
77
|
-
float *row_sq_out) {
|
|
76
|
+
float se_dot_and_row_sq_f32(const float *q, const float *row, size_t dim, float *row_sq_out) {
|
|
78
77
|
size_t j = 0;
|
|
79
78
|
#if defined(SE_HAVE_NEON)
|
|
80
79
|
float32x4_t d0 = vdupq_n_f32(0.0f);
|
|
@@ -93,6 +93,7 @@ RUBY_FUNC_EXPORTED void Init_static_embeddings(void);
|
|
|
93
93
|
typedef struct {
|
|
94
94
|
se_model_t model;
|
|
95
95
|
int open;
|
|
96
|
+
unsigned int active_ops;
|
|
96
97
|
} model_wrapper_t;
|
|
97
98
|
|
|
98
99
|
static void model_free(void *ptr) {
|
|
@@ -177,7 +178,7 @@ static void check_text_encoding_mode(VALUE str, long index, se_encoding_validati
|
|
|
177
178
|
rb_enc_name(enc));
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
int cr = ENC_CODERANGE(str);
|
|
181
|
+
int cr = (int)ENC_CODERANGE(str);
|
|
181
182
|
if (cr == ENC_CODERANGE_UNKNOWN) {
|
|
182
183
|
if (mode == SE_VALIDATE_ENCODING_PREFIX)
|
|
183
184
|
return;
|
|
@@ -216,6 +217,7 @@ typedef struct {
|
|
|
216
217
|
|
|
217
218
|
typedef struct {
|
|
218
219
|
const se_model_t *model;
|
|
220
|
+
model_wrapper_t *owner;
|
|
219
221
|
batch_input_t input;
|
|
220
222
|
uint32_t max_tokens;
|
|
221
223
|
float *out;
|
|
@@ -228,6 +230,8 @@ typedef struct {
|
|
|
228
230
|
se_error_t error;
|
|
229
231
|
} batch_job_t;
|
|
230
232
|
|
|
233
|
+
static void run_job(batch_job_t *job, size_t total_bytes);
|
|
234
|
+
|
|
231
235
|
static void job_fail(batch_job_t *job, se_error_t err, size_t index) {
|
|
232
236
|
if (job->failed)
|
|
233
237
|
return;
|
|
@@ -361,6 +365,21 @@ static VALUE fiber_worker_ensure(VALUE wrapper) {
|
|
|
361
365
|
return Qnil;
|
|
362
366
|
}
|
|
363
367
|
|
|
368
|
+
static VALUE run_job_body(VALUE arg) {
|
|
369
|
+
batch_job_t *job = (batch_job_t *)(uintptr_t)arg;
|
|
370
|
+
size_t total_bytes = 0;
|
|
371
|
+
for (size_t i = 0; i < job->input.count; i++)
|
|
372
|
+
total_bytes += job->input.lengths[i];
|
|
373
|
+
run_job(job, total_bytes);
|
|
374
|
+
return Qnil;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
static VALUE run_job_ensure(VALUE arg) {
|
|
378
|
+
batch_job_t *job = (batch_job_t *)(uintptr_t)arg;
|
|
379
|
+
job->owner->active_ops--;
|
|
380
|
+
return Qnil;
|
|
381
|
+
}
|
|
382
|
+
|
|
364
383
|
static void run_job(batch_job_t *job, size_t total_bytes) {
|
|
365
384
|
VALUE scheduler = current_fiber_scheduler();
|
|
366
385
|
|
|
@@ -386,11 +405,8 @@ static void run_job(batch_job_t *job, size_t total_bytes) {
|
|
|
386
405
|
|
|
387
406
|
static VALUE protected_run_job(VALUE arg) {
|
|
388
407
|
batch_job_t *job = (batch_job_t *)(uintptr_t)arg;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
total_bytes += job->input.lengths[i];
|
|
392
|
-
run_job(job, total_bytes);
|
|
393
|
-
return Qnil;
|
|
408
|
+
job->owner->active_ops++;
|
|
409
|
+
return rb_ensure(run_job_body, arg, run_job_ensure, arg);
|
|
394
410
|
}
|
|
395
411
|
|
|
396
412
|
static VALUE model_alloc(VALUE klass) {
|
|
@@ -422,6 +438,8 @@ static VALUE model_close(VALUE self) {
|
|
|
422
438
|
model_wrapper_t *w;
|
|
423
439
|
TypedData_Get_Struct(self, model_wrapper_t, &model_type, w);
|
|
424
440
|
if (w->open) {
|
|
441
|
+
if (w->active_ops != 0)
|
|
442
|
+
rb_raise(eError, "model is busy with an active operation");
|
|
425
443
|
se_model_close(&w->model);
|
|
426
444
|
w->open = 0;
|
|
427
445
|
}
|
|
@@ -474,18 +492,40 @@ typedef struct {
|
|
|
474
492
|
size_t pages;
|
|
475
493
|
} warmup_job_t;
|
|
476
494
|
|
|
495
|
+
typedef struct {
|
|
496
|
+
VALUE self;
|
|
497
|
+
model_wrapper_t *owner;
|
|
498
|
+
warmup_job_t job;
|
|
499
|
+
} warmup_run_t;
|
|
500
|
+
|
|
477
501
|
static void *warmup_execute(void *arg) {
|
|
478
502
|
warmup_job_t *job = (warmup_job_t *)arg;
|
|
479
503
|
job->pages = se_model_warmup(job->model);
|
|
480
504
|
return NULL;
|
|
481
505
|
}
|
|
482
506
|
|
|
507
|
+
static VALUE warmup_body(VALUE arg) {
|
|
508
|
+
warmup_run_t *run = (warmup_run_t *)(uintptr_t)arg;
|
|
509
|
+
rb_thread_call_without_gvl(warmup_execute, &run->job, NULL, NULL);
|
|
510
|
+
RB_GC_GUARD(run->self);
|
|
511
|
+
return Qnil;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
static VALUE warmup_ensure(VALUE arg) {
|
|
515
|
+
warmup_run_t *run = (warmup_run_t *)(uintptr_t)arg;
|
|
516
|
+
run->owner->active_ops--;
|
|
517
|
+
return Qnil;
|
|
518
|
+
}
|
|
519
|
+
|
|
483
520
|
static VALUE model_warmup(VALUE self) {
|
|
484
521
|
model_wrapper_t *w = get_model(self);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
522
|
+
warmup_run_t run;
|
|
523
|
+
run.self = self;
|
|
524
|
+
run.owner = w;
|
|
525
|
+
run.job.model = &w->model;
|
|
526
|
+
run.job.pages = 0;
|
|
527
|
+
w->active_ops++;
|
|
528
|
+
rb_ensure(warmup_body, (VALUE)(uintptr_t)&run, warmup_ensure, (VALUE)(uintptr_t)&run);
|
|
489
529
|
RB_GC_GUARD(self);
|
|
490
530
|
return self;
|
|
491
531
|
}
|
|
@@ -829,6 +869,7 @@ static VALUE embed_texts_internal(VALUE self, VALUE texts, int is_array, size_t
|
|
|
829
869
|
batch_job_t job;
|
|
830
870
|
memset(&job, 0, sizeof(job));
|
|
831
871
|
job.model = model;
|
|
872
|
+
job.owner = w;
|
|
832
873
|
job.max_tokens = max_tokens;
|
|
833
874
|
job.out = run.out;
|
|
834
875
|
job.stats = run.stats;
|
|
@@ -1116,6 +1157,7 @@ typedef struct {
|
|
|
1116
1157
|
|
|
1117
1158
|
typedef struct {
|
|
1118
1159
|
VALUE ids_value;
|
|
1160
|
+
model_wrapper_t *owner;
|
|
1119
1161
|
const se_model_t *model;
|
|
1120
1162
|
uint32_t max_tokens;
|
|
1121
1163
|
uint32_t *ids;
|
|
@@ -1126,6 +1168,7 @@ typedef struct {
|
|
|
1126
1168
|
se_token_stats_t *stats_out;
|
|
1127
1169
|
se_token_stats_t stats;
|
|
1128
1170
|
int truncated;
|
|
1171
|
+
int active;
|
|
1129
1172
|
} ids_run_t;
|
|
1130
1173
|
|
|
1131
1174
|
typedef struct {
|
|
@@ -1197,7 +1240,11 @@ static VALUE embed_token_ids_body(VALUE arg) {
|
|
|
1197
1240
|
se_error_clear(&job.error);
|
|
1198
1241
|
|
|
1199
1242
|
if (run->n >= SE_IDS_GVL_UNLOCK_THRESHOLD) {
|
|
1243
|
+
run->owner->active_ops++;
|
|
1244
|
+
run->active = 1;
|
|
1200
1245
|
rb_thread_call_without_gvl(ids_execute, &job, ids_unblock_cancel, &job);
|
|
1246
|
+
run->owner->active_ops--;
|
|
1247
|
+
run->active = 0;
|
|
1201
1248
|
rb_thread_check_ints();
|
|
1202
1249
|
if (job.cancelled)
|
|
1203
1250
|
rb_raise(rb_eInterrupt, "operation cancelled");
|
|
@@ -1225,6 +1272,8 @@ static VALUE embed_token_ids_body(VALUE arg) {
|
|
|
1225
1272
|
|
|
1226
1273
|
static VALUE embed_token_ids_ensure(VALUE arg) {
|
|
1227
1274
|
ids_run_t *run = (ids_run_t *)(uintptr_t)arg;
|
|
1275
|
+
if (run->active)
|
|
1276
|
+
run->owner->active_ops--;
|
|
1228
1277
|
se_free(run->ids);
|
|
1229
1278
|
se_free(run->out);
|
|
1230
1279
|
run->ids = NULL;
|
|
@@ -1280,6 +1329,7 @@ static VALUE embed_token_ids_value(VALUE self, VALUE ids_value, VALUE max_tokens
|
|
|
1280
1329
|
ids_run_t run;
|
|
1281
1330
|
memset(&run, 0, sizeof(run));
|
|
1282
1331
|
run.ids_value = ids_value;
|
|
1332
|
+
run.owner = w;
|
|
1283
1333
|
run.model = &w->model;
|
|
1284
1334
|
run.max_tokens = max_tokens;
|
|
1285
1335
|
run.n = n;
|
|
Binary file
|