static_embeddings 0.1.3 → 0.1.4
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 +85 -0
- data/README.md +6 -5
- data/docs/ARCHITECTURE.md +12 -4
- data/docs/LIMITATIONS.md +4 -2
- data/docs/MODEL_AUDIT.md +17 -9
- data/docs/PERFORMANCE.md +28 -19
- data/ext/static_embeddings/se_embed.c +46 -7
- data/ext/static_embeddings/se_f16.c +43 -7
- data/ext/static_embeddings/se_format.c +80 -5
- data/ext/static_embeddings/se_internal.h +9 -0
- data/ext/static_embeddings/se_tokenizer.c +301 -34
- data/ext/static_embeddings/static_embeddings.c +105 -47
- data/lib/static_embeddings/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f72b45c2d6fa5310e3b6d5a9b0a86ab0430fc4af71f6811f9aa02655de6272a8
|
|
4
|
+
data.tar.gz: 51f0bb96a3b0adc5f2799e1df425cf1747ca521d92ae90afba59e0014f1d4106
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53630b742cd2723656b5d826a82f57042dcaab1e3259758aa6a71eac20ba387a23b3fb3905f0f36f3ce641b122229f366c9af1f029205b44cd82110f538a49cb
|
|
7
|
+
data.tar.gz: 199cd93e9898b7457d5e19b9cf1857b4d78ffc46777ca26778b094fa70ee02559dc90733e4e815eba74297ef9c066940792cef09e022801ef48c704e868fa8db
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,90 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4 (unreleased)
|
|
4
|
+
|
|
5
|
+
Hot-path work in the C runtime. Token ids, the pooling contract and f16
|
|
6
|
+
rounding are unchanged: the ASCII parity sweep and the differential fuzz
|
|
7
|
+
digest against `StaticEmbeddings::Reference` still match. The
|
|
8
|
+
`model2vec.StaticModel` oracle was re-run for 0.1.4 against the same
|
|
9
|
+
`potion-retrieval-32m` snapshot and `.semb` as 0.1.3: 31/31 id rows, 31/31
|
|
10
|
+
vectors, `min_cosine=0.9999999999989528`, `max_abs_all=2.980232238769531e-07`
|
|
11
|
+
— the same printed numbers as 0.1.3. L2 still accumulates sum-of-squares in
|
|
12
|
+
double; 0.1.4 does that with SIMD pairwise adds.
|
|
13
|
+
|
|
14
|
+
Measured on an M1 Pro, `potion-retrieval-32m`, `samples/run_all.sh`
|
|
15
|
+
`DURATION=25`, production build (no alloc-stats). The 0.1.3 comparison run
|
|
16
|
+
had `STATIC_EMBEDDINGS_ALLOC_STATS=1`, which the docs cost at about 3% on
|
|
17
|
+
`embed` and 13% on `tokenize`; differences smaller than that, or inside the
|
|
18
|
+
usual 5–10% laptop spread, are not claimed.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **L2 normalisation is a SIMD double reduction plus one scale into `out`.**
|
|
23
|
+
It used to `scale_copy` with `1.0` and then walk the vector twice in scalar
|
|
24
|
+
double. On this model `dim=512` and short in-vocabulary English, that L2
|
|
25
|
+
slot was about 19% of an ASCII `embed_batch` sample tree. The fused path
|
|
26
|
+
has a NEON/`__aarch64__` kernel and an SSE2 kernel; the scalar tail is
|
|
27
|
+
unchanged. ASCII batch went 307k → 338k texts/s on the M1 Pro (+10%). The
|
|
28
|
+
SSE2 path is compiled, not timed.
|
|
29
|
+
|
|
30
|
+
- **The AArch64 f16 top-k kernel decodes 16 halves per iteration, not 8.**
|
|
31
|
+
The x86 F16C kernel is untouched. Same 50 000-row, `k=10`, ASCII matrix:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
f32 f16
|
|
35
|
+
x86-64, f16c 3.00 ms 1.65 ms f16 1.8x faster (0.1.2, F16C)
|
|
36
|
+
M1 Pro, neon-fp16 2.36 ms 1.81 ms f16 1.3x faster (0.1.4)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The M1 Pro row used to read 2.29 / 2.88 ms, f16 1.3x *slower*. `docs/PERFORMANCE.md`
|
|
40
|
+
and the README follow the new numbers. On the lookup-table fallback f16 is
|
|
41
|
+
still usually slower than f32.
|
|
42
|
+
|
|
43
|
+
- **Scratch buffers are reused per OS thread, and freed when the thread
|
|
44
|
+
exits.** `embed`, `tokenize`, `embed_batch` and `embed_token_ids` used to
|
|
45
|
+
`malloc`/`free` a scratch set on every call. A first cut of 0.1.4 used
|
|
46
|
+
`__thread` storage with no destructor, which leaked the heap buffers inside
|
|
47
|
+
the slot — bounded for a Puma pool, unbounded on the fiber-scheduler path
|
|
48
|
+
that `rb_thread_create`s one OS thread per large call. The slot is now a
|
|
49
|
+
`pthread_key` / `FlsAlloc` value whose destructor frees it. On release the
|
|
50
|
+
slot is trimmed back to the reserve sizes, so one `max_tokens: false`
|
|
51
|
+
document does not pin megabytes on that thread until process exit.
|
|
52
|
+
`tokenize` and the small `embed` path release the slot through `rb_ensure`,
|
|
53
|
+
so `rb_ary_push` raising cannot leave `in_use` stuck. `memory_smoke` calls
|
|
54
|
+
acquire/release so ASan/valgrind actually see this code.
|
|
55
|
+
|
|
56
|
+
- **ASCII WordPiece copies an all-ASCII word as bytes** instead of
|
|
57
|
+
encoding UTF-8 through the codepoint buffer, then hashes and splits on the
|
|
58
|
+
trie with identity offsets. Output is the same ids. On the OOV tokenize
|
|
59
|
+
probes this is inside the run-to-run floor.
|
|
60
|
+
|
|
61
|
+
- **Latin-1 (`U+0080..U+00FF`) lower/NFD/Mn/P/C/Zs tables are built at
|
|
62
|
+
load.** Codepoints below 256 skip the binary search into the mmap'd maps.
|
|
63
|
+
ASCII (`< 0x80`) still uses the existing 128-entry class table. Not visible
|
|
64
|
+
as texts/s on the English batch corpus.
|
|
65
|
+
|
|
66
|
+
- **Vocabulary compare is an inline unaligned equality test** instead of
|
|
67
|
+
libc `memcmp`. Trie child lookup special-cases one- and two-edge nodes
|
|
68
|
+
before the linear/binary search. Same ids; no measured throughput claim.
|
|
69
|
+
|
|
70
|
+
Two things were measured and not shipped. Unrolling `add_row` to 32 floats
|
|
71
|
+
and stretching the prefetch distance to 8 cost about 13% on
|
|
72
|
+
`random_pooling_hot_path` — that loop is already memory-bound on random 2 KiB
|
|
73
|
+
row gathers. Walking the ASCII trie without `cps2` (`start += matched_len`)
|
|
74
|
+
cost about 38% on the OOV tokenize probes.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **Batch samples print `mean_tokens_per_text`, `unk_ratio` and
|
|
79
|
+
`tokens_per_sec`.** 307k vs 103k texts/s is not readable without token
|
|
80
|
+
density: on this corpus ASCII is 31 tokens/text and 0 unk, hashes 73 and 0,
|
|
81
|
+
unicode 68 and 4.4% unk.
|
|
82
|
+
|
|
83
|
+
- **`test/scratch_tls_test.rb`.** When the instrumented allocator is loaded:
|
|
84
|
+
scratch bytes plateau across eight generations of helper threads, and a
|
|
85
|
+
large `max_tokens: false` embed must not leave the calling thread's slot
|
|
86
|
+
pinned. The `alloc_stats` CI job is what actually runs it.
|
|
87
|
+
|
|
3
88
|
## 0.1.3 (unreleased)
|
|
4
89
|
|
|
5
90
|
Hardening and tooling, mostly borrowed from the sibling `tg_geometry` gem after
|
data/README.md
CHANGED
|
@@ -132,10 +132,11 @@ in float32. For a 512-dimensional model one vector goes from 2048 to 1024 bytes.
|
|
|
132
132
|
It is accepted by `embed`, `embed_batch`, `embed_with_stats`, `embed_token_ids`,
|
|
133
133
|
`embed_token_ids_with_stats`, `pack`, `unpack`, and the top-k helpers.
|
|
134
134
|
|
|
135
|
-
Choose `f16` for the RAM and storage it saves
|
|
136
|
-
bytes a top-k scan streams, but
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
Choose `f16` for the RAM and storage it saves; speed is a property of the
|
|
136
|
+
machine and of the decode kernel. It halves the bytes a top-k scan streams, but
|
|
137
|
+
every row still has to be decoded before scoring — this repository's own
|
|
138
|
+
samples show `f16` winning on x86-64 F16C and, as of 0.1.4, on M1 Pro neon-fp16
|
|
139
|
+
as well. On the lookup-table fallback it usually loses. See
|
|
139
140
|
`docs/PERFORMANCE.md` before assuming either.
|
|
140
141
|
|
|
141
142
|
`StaticEmbeddings.simd_backend` reports the live kernel: `"neon-fp16"`,
|
|
@@ -323,7 +324,7 @@ cc -O2 -std=c99 -Wall -Wextra -Iext/static_embeddings \
|
|
|
323
324
|
ext/static_embeddings/se_unicode.c \
|
|
324
325
|
ext/static_embeddings/se_tokenizer.c \
|
|
325
326
|
ext/static_embeddings/se_embed.c \
|
|
326
|
-
-lm -o tmp/memory_smoke
|
|
327
|
+
-lm -pthread -o tmp/memory_smoke
|
|
327
328
|
./tmp/memory_smoke tmp/test-tiny.semb
|
|
328
329
|
```
|
|
329
330
|
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -174,10 +174,13 @@ storage/transport choice, not a different model. `embed_array` and
|
|
|
174
174
|
|
|
175
175
|
The f32 compute path uses small explicit SIMD kernels where they keep the format
|
|
176
176
|
simple: row accumulation and scaling use NEON/SSE when the compiler target
|
|
177
|
-
exposes them,
|
|
177
|
+
exposes them, L2 sum-of-squares uses SIMD double (NEON on AArch64, SSE2 on
|
|
178
|
+
x86) so the inverse stays a `1/sqrt` of a double accumulation, and there is a
|
|
179
|
+
scalar fallback everywhere else. `dot_top_k`/`cosine_top_k`
|
|
178
180
|
share the SIMD dot kernel for `format: :f32`; `cosine_top_k` additionally
|
|
179
181
|
accumulates each row's sum of squares so it can divide by the true norms.
|
|
180
|
-
`format: :f16` top-k decodes half components on the fly
|
|
182
|
+
`format: :f16` top-k decodes half components on the fly (16-wide on AArch64
|
|
183
|
+
NEON-FP16, 16-wide on x86 F16C). What is still
|
|
181
184
|
deliberately absent is a dependency on BLAS: pooling is gathering random
|
|
182
185
|
embedding rows, not a dense matrix multiply.
|
|
183
186
|
|
|
@@ -293,8 +296,13 @@ cadence in bytes so a long ASCII run is no less interruptible. The pooling loop
|
|
|
293
296
|
checks every 256 rows, and the top-k scan every 1024 rows. `unblock_cancel` sets that flag when Ruby
|
|
294
297
|
interrupts a GVL-free region.
|
|
295
298
|
|
|
296
|
-
No global mutable state exists in C. The model is immutable after load
|
|
297
|
-
|
|
299
|
+
No global mutable state exists in C. The model is immutable after load.
|
|
300
|
+
Scratch buffers are reused per OS thread via `pthread_key` / `FlsAlloc`, and
|
|
301
|
+
the destructor frees them when the thread exits — including the short-lived
|
|
302
|
+
threads the fiber scheduler path creates per large call. On release the slot
|
|
303
|
+
is trimmed back to the reserve sizes, so a single unlimited-`max_tokens`
|
|
304
|
+
document does not pin its working set until the thread dies. A nested call on
|
|
305
|
+
the same thread allocates a one-off heap scratch that is freed on release.
|
|
298
306
|
|
|
299
307
|
## Where this fits in a RAG pipeline
|
|
300
308
|
|
data/docs/LIMITATIONS.md
CHANGED
|
@@ -57,8 +57,10 @@ the entry says whether that is a decision or just unfinished work.
|
|
|
57
57
|
## Storage formats
|
|
58
58
|
|
|
59
59
|
- **`f32` and `f16` only.** No int8, no binary quantisation.
|
|
60
|
-
- **`f16` is a storage
|
|
61
|
-
|
|
60
|
+
- **`f16` is a storage encoding, not a different model.** Top-k over an f16
|
|
61
|
+
matrix can be faster or slower than f32 depending on the decode kernel; see
|
|
62
|
+
`docs/PERFORMANCE.md`. `embed_batch(format: :f16)` still encodes the blob
|
|
63
|
+
with the scalar converter.
|
|
62
64
|
- **`f16` rounding is half-up, not ties-to-even.** Blobs written by this gem can
|
|
63
65
|
differ from NumPy or PyTorch by one ULP on exact halfway values.
|
|
64
66
|
|
data/docs/MODEL_AUDIT.md
CHANGED
|
@@ -12,26 +12,28 @@ runtime half of it even though the file bytes are untouched.
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| 0.1.1 | `parity OK`, recorded below | superseded |
|
|
14
14
|
| 0.1.2 | **not re-run** | superseded before release |
|
|
15
|
-
| 0.1.3 | `parity OK
|
|
15
|
+
| 0.1.3 | `parity OK` | tokenizer / pooling contract; same numbers as 0.1.4 |
|
|
16
|
+
| 0.1.4 | `parity OK`, recorded below | current |
|
|
16
17
|
|
|
17
18
|
0.1.2 changed `is_control()`, which changes token ids for any input containing
|
|
18
19
|
`U+007F`. `StaticEmbeddings::Reference` cannot settle whether the new behaviour
|
|
19
20
|
matches HuggingFace, because it is an implementation twin of the C runtime
|
|
20
21
|
written in this repository. The 0.1.3 release candidate was checked against a
|
|
21
22
|
fresh upstream `model2vec.StaticModel` oracle that includes DEL, control
|
|
22
|
-
characters, Unicode, OOV, long-word and truncation rows.
|
|
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`.
|
|
23
25
|
|
|
24
26
|
Source model:
|
|
25
27
|
|
|
26
28
|
- Hugging Face repository: `minishlab/potion-retrieval-32M`
|
|
27
29
|
- Hugging Face snapshot: `6fc8051fab2a1e0ee76689cf08c853792ac285e7`
|
|
28
30
|
- Oracle implementation: `model2vec.StaticModel.from_pretrained`
|
|
29
|
-
- Python package: `model2vec 0.9.0`
|
|
31
|
+
- Python package: `model2vec 0.9.0` (`tokenizers 0.23.1`, `numpy 2.5.2`)
|
|
30
32
|
- Oracle file: `tmp/model2vec_oracle.json`
|
|
31
33
|
- Oracle rows in this recorded run: `31`
|
|
32
34
|
- Oracle dimension: `512`
|
|
33
35
|
- Oracle max length: `512`
|
|
34
|
-
- Runtime at time of this record: `static_embeddings 0.1.
|
|
36
|
+
- Runtime at time of this record: `static_embeddings 0.1.4`
|
|
35
37
|
|
|
36
38
|
Converted `.semb`:
|
|
37
39
|
|
|
@@ -44,7 +46,8 @@ Converted `.semb`:
|
|
|
44
46
|
Parity command:
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
|
-
python tools/model2vec_oracle.py
|
|
49
|
+
python tools/model2vec_oracle.py \
|
|
50
|
+
~/.cache/huggingface/hub/models--minishlab--potion-retrieval-32M/snapshots/6fc8051fab2a1e0ee76689cf08c853792ac285e7 \
|
|
48
51
|
--out tmp/model2vec_oracle.json
|
|
49
52
|
|
|
50
53
|
bundle exec rake parity \
|
|
@@ -52,7 +55,7 @@ bundle exec rake parity \
|
|
|
52
55
|
ORACLE=tmp/model2vec_oracle.json
|
|
53
56
|
```
|
|
54
57
|
|
|
55
|
-
Parity result:
|
|
58
|
+
Parity result (0.1.4, 2026-08-31):
|
|
56
59
|
|
|
57
60
|
```text
|
|
58
61
|
rows=31
|
|
@@ -64,6 +67,11 @@ vector_failures=[]
|
|
|
64
67
|
parity OK
|
|
65
68
|
```
|
|
66
69
|
|
|
70
|
+
The printed `min_cosine` and `max_abs_all` are identical to the 0.1.3 record.
|
|
71
|
+
Worst vector row is still idx=29 (`"word" + 400 spaces`, 80 800 bytes) at
|
|
72
|
+
`max_abs=2.9802322e-07`. SIMD pairwise double L2 did not move the oracle
|
|
73
|
+
agreement on this corpus.
|
|
74
|
+
|
|
67
75
|
Decision:
|
|
68
76
|
|
|
69
77
|
- Tokenization parity: pass
|
|
@@ -75,6 +83,6 @@ Decision:
|
|
|
75
83
|
- Long input / truncation behavior: pass
|
|
76
84
|
- DEL / control-character behavior: pass
|
|
77
85
|
|
|
78
|
-
Accepted for runtime and benchmark use **under 0.1.
|
|
79
|
-
unchanged and its SHA256 still matches
|
|
80
|
-
refreshed after the
|
|
86
|
+
Accepted for runtime and benchmark use **under 0.1.4**. The `.semb` file is
|
|
87
|
+
unchanged and its SHA256 still matches. The runtime half of the record was
|
|
88
|
+
refreshed after the 0.1.4 L2 kernel change.
|
data/docs/PERFORMANCE.md
CHANGED
|
@@ -22,33 +22,42 @@ ruby tools/benchmark.rb [model.semb]
|
|
|
22
22
|
|
|
23
23
|
## Where the time actually goes
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
Which file to profile depends on the model and the text. On a short-dimension
|
|
26
|
+
demo model, or on synthetic all-OOV input, tokenization dominates. On
|
|
27
|
+
`potion-retrieval-32m` (`dim=512`) with in-vocabulary English, pooling is the
|
|
28
|
+
larger share: each token is a random 2 KiB row add, and before 0.1.4 the scalar
|
|
29
|
+
L2 pass showed up as about 19% of an ASCII `embed_batch` sample tree. SIMD in
|
|
30
|
+
that L2 slot was a real win; another unroll of the add loop was not — it is
|
|
31
|
+
memory-bound on those gathers, and the compiler already vectorises it.
|
|
32
|
+
|
|
33
|
+
Out-of-vocabulary text is the remaining tokenizer cliff. In-vocabulary words
|
|
34
|
+
hit the vocabulary hash directly; everything else falls through to trie-driven
|
|
35
|
+
subword splitting, which on a synthetic all-OOV corpus measured several times
|
|
36
|
+
the per-text cost. Non-English input on an English model pays this on top of
|
|
37
|
+
the `[UNK]` quality problem. Quote `mean_tokens_per_text` and `unk_ratio` from
|
|
38
|
+
the batch samples before comparing texts/s across modes: hashes are slower
|
|
39
|
+
than ASCII on this corpus mostly because they emit ~73 tokens/text against
|
|
40
|
+
~31, not only because they miss the hash.
|
|
35
41
|
|
|
36
42
|
## `f16` is a storage trade-off
|
|
37
43
|
|
|
38
|
-
`format: :f16` halves the bytes a top-k scan streams and
|
|
39
|
-
|
|
40
|
-
sample runs disagree with each other — same corpus, same
|
|
41
|
-
kernel in both cases:
|
|
44
|
+
`format: :f16` halves the bytes a top-k scan streams and costs a decode per
|
|
45
|
+
row. Which one wins is a property of the machine and of the decode kernel, and
|
|
46
|
+
this repository's own sample runs disagree with each other — same corpus, same
|
|
47
|
+
`k`, native decode kernel in both cases:
|
|
42
48
|
|
|
43
49
|
```text
|
|
44
50
|
f32 f16
|
|
45
|
-
x86-64, f16c 3.00 ms 1.65 ms f16 1.8x faster
|
|
46
|
-
M1 Pro, neon-fp16 2.
|
|
51
|
+
x86-64, f16c 3.00 ms 1.65 ms f16 1.8x faster (F16C kernel, unchanged)
|
|
52
|
+
M1 Pro, neon-fp16 2.36 ms 1.81 ms f16 1.3x faster (0.1.4, 16-wide NEON)
|
|
47
53
|
```
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
The M1 Pro row in 0.1.3 was 2.29 / 2.88 ms, f16 1.3x *slower*, with an 8-wide
|
|
56
|
+
decode. Neither ratio transfers. Confirm `StaticEmbeddings.simd_backend`, then
|
|
57
|
+
measure on the hardware you will run on. On the lookup-table fallback `f16` is
|
|
58
|
+
usually slower than `f32`. `embed_batch(format: :f16)` still encodes the
|
|
59
|
+
returned blob with the scalar half converter, so a batch that is not a top-k
|
|
60
|
+
scan can still be slower than `f32` on the same machine.
|
|
52
61
|
|
|
53
62
|
## C allocation counters
|
|
54
63
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#define SE_HAVE_NEON 1
|
|
9
9
|
#elif defined(__SSE__)
|
|
10
10
|
#include <xmmintrin.h>
|
|
11
|
+
#include <emmintrin.h>
|
|
11
12
|
#define SE_HAVE_SSE 1
|
|
12
13
|
#endif
|
|
13
14
|
|
|
@@ -71,15 +72,49 @@ static void scale_copy(float *out, const float *acc, uint32_t dim, float inv) {
|
|
|
71
72
|
out[j] = acc[j] * inv;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
static double sum_sq_d(const float *vec, uint32_t dim) {
|
|
76
|
+
uint32_t j = 0;
|
|
75
77
|
double sum = 0.0;
|
|
76
|
-
|
|
78
|
+
#if defined(SE_HAVE_NEON) && defined(__aarch64__)
|
|
79
|
+
float64x2_t s0 = vdupq_n_f64(0.0);
|
|
80
|
+
float64x2_t s1 = vdupq_n_f64(0.0);
|
|
81
|
+
for (; j + 7 < dim; j += 8) {
|
|
82
|
+
float32x4_t a = vld1q_f32(vec + j);
|
|
83
|
+
float32x4_t b = vld1q_f32(vec + j + 4);
|
|
84
|
+
float64x2_t a0 = vcvt_f64_f32(vget_low_f32(a));
|
|
85
|
+
float64x2_t a1 = vcvt_f64_f32(vget_high_f32(a));
|
|
86
|
+
float64x2_t b0 = vcvt_f64_f32(vget_low_f32(b));
|
|
87
|
+
float64x2_t b1 = vcvt_f64_f32(vget_high_f32(b));
|
|
88
|
+
s0 = vaddq_f64(s0, vmulq_f64(a0, a0));
|
|
89
|
+
s1 = vaddq_f64(s1, vmulq_f64(a1, a1));
|
|
90
|
+
s0 = vaddq_f64(s0, vmulq_f64(b0, b0));
|
|
91
|
+
s1 = vaddq_f64(s1, vmulq_f64(b1, b1));
|
|
92
|
+
}
|
|
93
|
+
sum = vaddvq_f64(vaddq_f64(s0, s1));
|
|
94
|
+
#elif defined(SE_HAVE_SSE)
|
|
95
|
+
__m128d s0 = _mm_setzero_pd();
|
|
96
|
+
__m128d s1 = _mm_setzero_pd();
|
|
97
|
+
for (; j + 3 < dim; j += 4) {
|
|
98
|
+
__m128 v = _mm_loadu_ps(vec + j);
|
|
99
|
+
__m128d lo = _mm_cvtps_pd(v);
|
|
100
|
+
__m128d hi = _mm_cvtps_pd(_mm_movehl_ps(v, v));
|
|
101
|
+
s0 = _mm_add_pd(s0, _mm_mul_pd(lo, lo));
|
|
102
|
+
s1 = _mm_add_pd(s1, _mm_mul_pd(hi, hi));
|
|
103
|
+
}
|
|
104
|
+
double tmp[2];
|
|
105
|
+
_mm_storeu_pd(tmp, _mm_add_pd(s0, s1));
|
|
106
|
+
sum = tmp[0] + tmp[1];
|
|
107
|
+
#endif
|
|
108
|
+
for (; j < dim; j++)
|
|
77
109
|
sum += (double)vec[j] * (double)vec[j];
|
|
110
|
+
return sum;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
void se_l2_normalize(float *vec, uint32_t dim) {
|
|
114
|
+
double sum = sum_sq_d(vec, dim);
|
|
78
115
|
if (sum <= 0.0)
|
|
79
116
|
return;
|
|
80
|
-
|
|
81
|
-
for (uint32_t j = 0; j < dim; j++)
|
|
82
|
-
vec[j] *= inv;
|
|
117
|
+
scale_copy(vec, vec, dim, (float)(1.0 / sqrt(sum)));
|
|
83
118
|
}
|
|
84
119
|
|
|
85
120
|
static se_status_t embed_ids_core(const se_model_t *model, se_scratch_t *sc, const uint32_t *ids,
|
|
@@ -128,8 +163,12 @@ static se_status_t embed_ids_core(const se_model_t *model, se_scratch_t *sc, con
|
|
|
128
163
|
}
|
|
129
164
|
|
|
130
165
|
if (model->meta.normalization_type == SE_NORMALIZATION_L2) {
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
double sum = sum_sq_d(acc, dim);
|
|
167
|
+
if (sum <= 0.0) {
|
|
168
|
+
memset(out, 0, (size_t)dim * sizeof(float));
|
|
169
|
+
return SE_OK;
|
|
170
|
+
}
|
|
171
|
+
scale_copy(out, acc, dim, (float)(1.0 / sqrt(sum)));
|
|
133
172
|
} else {
|
|
134
173
|
const float inv = 1.0f / (float)used;
|
|
135
174
|
scale_copy(out, acc, dim, inv);
|
|
@@ -221,17 +221,31 @@ static float dot_product_f16_neon(const float *q, const uint8_t *row, size_t dim
|
|
|
221
221
|
size_t j = 0;
|
|
222
222
|
float32x4_t a0 = vdupq_n_f32(0.0f);
|
|
223
223
|
float32x4_t a1 = vdupq_n_f32(0.0f);
|
|
224
|
+
float32x4_t a2 = vdupq_n_f32(0.0f);
|
|
225
|
+
float32x4_t a3 = vdupq_n_f32(0.0f);
|
|
226
|
+
for (; j + 15 < dim; j += 16) {
|
|
227
|
+
float16x8_t h0 =
|
|
228
|
+
vreinterpretq_f16_u16(vld1q_u16((const uint16_t *)(const void *)(row + j * 2)));
|
|
229
|
+
float16x8_t h1 =
|
|
230
|
+
vreinterpretq_f16_u16(vld1q_u16((const uint16_t *)(const void *)(row + (j + 8) * 2)));
|
|
231
|
+
float32x4_t r0 = vcvt_f32_f16(vget_low_f16(h0));
|
|
232
|
+
float32x4_t r1 = vcvt_f32_f16(vget_high_f16(h0));
|
|
233
|
+
float32x4_t r2 = vcvt_f32_f16(vget_low_f16(h1));
|
|
234
|
+
float32x4_t r3 = vcvt_f32_f16(vget_high_f16(h1));
|
|
235
|
+
a0 = vmlaq_f32(a0, vld1q_f32(q + j), r0);
|
|
236
|
+
a1 = vmlaq_f32(a1, vld1q_f32(q + j + 4), r1);
|
|
237
|
+
a2 = vmlaq_f32(a2, vld1q_f32(q + j + 8), r2);
|
|
238
|
+
a3 = vmlaq_f32(a3, vld1q_f32(q + j + 12), r3);
|
|
239
|
+
}
|
|
224
240
|
for (; j + 7 < dim; j += 8) {
|
|
225
241
|
float16x4_t h0 =
|
|
226
242
|
vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + j * 2)));
|
|
227
243
|
float16x4_t h1 =
|
|
228
244
|
vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + (j + 4) * 2)));
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
a0 = vmlaq_f32(a0, vld1q_f32(q + j), r0);
|
|
232
|
-
a1 = vmlaq_f32(a1, vld1q_f32(q + j + 4), r1);
|
|
245
|
+
a0 = vmlaq_f32(a0, vld1q_f32(q + j), vcvt_f32_f16(h0));
|
|
246
|
+
a1 = vmlaq_f32(a1, vld1q_f32(q + j + 4), vcvt_f32_f16(h1));
|
|
233
247
|
}
|
|
234
|
-
float32x4_t sumv = vaddq_f32(a0, a1);
|
|
248
|
+
float32x4_t sumv = vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3));
|
|
235
249
|
float dot = vaddvq_f32(sumv);
|
|
236
250
|
for (; j < dim; j++)
|
|
237
251
|
dot += q[j] * se_f16_bits_to_float(read_u16le(row + j * 2));
|
|
@@ -243,8 +257,30 @@ static float dot_and_row_sq_f16_neon(const float *q, const uint8_t *row, size_t
|
|
|
243
257
|
size_t j = 0;
|
|
244
258
|
float32x4_t d0 = vdupq_n_f32(0.0f);
|
|
245
259
|
float32x4_t d1 = vdupq_n_f32(0.0f);
|
|
260
|
+
float32x4_t d2 = vdupq_n_f32(0.0f);
|
|
261
|
+
float32x4_t d3 = vdupq_n_f32(0.0f);
|
|
246
262
|
float32x4_t s0 = vdupq_n_f32(0.0f);
|
|
247
263
|
float32x4_t s1 = vdupq_n_f32(0.0f);
|
|
264
|
+
float32x4_t s2 = vdupq_n_f32(0.0f);
|
|
265
|
+
float32x4_t s3 = vdupq_n_f32(0.0f);
|
|
266
|
+
for (; j + 15 < dim; j += 16) {
|
|
267
|
+
float16x8_t h0 =
|
|
268
|
+
vreinterpretq_f16_u16(vld1q_u16((const uint16_t *)(const void *)(row + j * 2)));
|
|
269
|
+
float16x8_t h1 =
|
|
270
|
+
vreinterpretq_f16_u16(vld1q_u16((const uint16_t *)(const void *)(row + (j + 8) * 2)));
|
|
271
|
+
float32x4_t r0 = vcvt_f32_f16(vget_low_f16(h0));
|
|
272
|
+
float32x4_t r1 = vcvt_f32_f16(vget_high_f16(h0));
|
|
273
|
+
float32x4_t r2 = vcvt_f32_f16(vget_low_f16(h1));
|
|
274
|
+
float32x4_t r3 = vcvt_f32_f16(vget_high_f16(h1));
|
|
275
|
+
d0 = vmlaq_f32(d0, vld1q_f32(q + j), r0);
|
|
276
|
+
d1 = vmlaq_f32(d1, vld1q_f32(q + j + 4), r1);
|
|
277
|
+
d2 = vmlaq_f32(d2, vld1q_f32(q + j + 8), r2);
|
|
278
|
+
d3 = vmlaq_f32(d3, vld1q_f32(q + j + 12), r3);
|
|
279
|
+
s0 = vmlaq_f32(s0, r0, r0);
|
|
280
|
+
s1 = vmlaq_f32(s1, r1, r1);
|
|
281
|
+
s2 = vmlaq_f32(s2, r2, r2);
|
|
282
|
+
s3 = vmlaq_f32(s3, r3, r3);
|
|
283
|
+
}
|
|
248
284
|
for (; j + 7 < dim; j += 8) {
|
|
249
285
|
float16x4_t h0 =
|
|
250
286
|
vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + j * 2)));
|
|
@@ -257,8 +293,8 @@ static float dot_and_row_sq_f16_neon(const float *q, const uint8_t *row, size_t
|
|
|
257
293
|
s0 = vmlaq_f32(s0, r0, r0);
|
|
258
294
|
s1 = vmlaq_f32(s1, r1, r1);
|
|
259
295
|
}
|
|
260
|
-
float dot = vaddvq_f32(vaddq_f32(d0, d1));
|
|
261
|
-
float row_sq = vaddvq_f32(vaddq_f32(s0, s1));
|
|
296
|
+
float dot = vaddvq_f32(vaddq_f32(vaddq_f32(d0, d1), vaddq_f32(d2, d3)));
|
|
297
|
+
float row_sq = vaddvq_f32(vaddq_f32(vaddq_f32(s0, s1), vaddq_f32(s2, s3)));
|
|
262
298
|
for (; j < dim; j++) {
|
|
263
299
|
float r = se_f16_bits_to_float(read_u16le(row + j * 2));
|
|
264
300
|
dot += q[j] * r;
|
|
@@ -61,6 +61,34 @@ static uint32_t hash_bytes_continue(uint32_t h, const uint8_t *data, size_t len)
|
|
|
61
61
|
return h;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
static int se_memeq(const uint8_t *a, const uint8_t *b, size_t n) {
|
|
65
|
+
while (n >= 8) {
|
|
66
|
+
uint64_t ua, ub;
|
|
67
|
+
memcpy(&ua, a, 8);
|
|
68
|
+
memcpy(&ub, b, 8);
|
|
69
|
+
if (ua != ub)
|
|
70
|
+
return 0;
|
|
71
|
+
a += 8;
|
|
72
|
+
b += 8;
|
|
73
|
+
n -= 8;
|
|
74
|
+
}
|
|
75
|
+
if (n >= 4) {
|
|
76
|
+
uint32_t ua, ub;
|
|
77
|
+
memcpy(&ua, a, 4);
|
|
78
|
+
memcpy(&ub, b, 4);
|
|
79
|
+
if (ua != ub)
|
|
80
|
+
return 0;
|
|
81
|
+
a += 4;
|
|
82
|
+
b += 4;
|
|
83
|
+
n -= 4;
|
|
84
|
+
}
|
|
85
|
+
while (n--) {
|
|
86
|
+
if (*a++ != *b++)
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
64
92
|
int se_vocab_lookup_piece(const se_model_t *model, const uint8_t *prefix, size_t prefix_len,
|
|
65
93
|
const uint8_t *bytes, size_t len, uint32_t *id_out) {
|
|
66
94
|
if (prefix_len > UINT32_MAX || len > UINT32_MAX || prefix_len > UINT32_MAX - len)
|
|
@@ -79,9 +107,9 @@ int se_vocab_lookup_piece(const se_model_t *model, const uint8_t *prefix, size_t
|
|
|
79
107
|
if (slot->token_id == SE_SLOT_EMPTY)
|
|
80
108
|
return 0;
|
|
81
109
|
if (slot->hash == h && slot->str_len == total_len) {
|
|
82
|
-
const
|
|
83
|
-
if ((prefix_len == 0 ||
|
|
84
|
-
(len == 0 ||
|
|
110
|
+
const uint8_t *token = (const uint8_t *)(model->vocab_strings + slot->str_off);
|
|
111
|
+
if ((prefix_len == 0 || se_memeq(token, prefix, prefix_len)) &&
|
|
112
|
+
(len == 0 || se_memeq(token + prefix_len, bytes, len))) {
|
|
85
113
|
*id_out = slot->token_id;
|
|
86
114
|
return 1;
|
|
87
115
|
}
|
|
@@ -103,6 +131,17 @@ static inline uint32_t trie_find_child(const se_trie_t *trie, const se_trie_node
|
|
|
103
131
|
if (count == 0)
|
|
104
132
|
return SE_SLOT_EMPTY;
|
|
105
133
|
|
|
134
|
+
if (count == 1)
|
|
135
|
+
return trie->edges[start].byte == byte ? trie->edges[start].child : SE_SLOT_EMPTY;
|
|
136
|
+
|
|
137
|
+
if (count == 2) {
|
|
138
|
+
if (trie->edges[start].byte == byte)
|
|
139
|
+
return trie->edges[start].child;
|
|
140
|
+
if (trie->edges[start + 1u].byte == byte)
|
|
141
|
+
return trie->edges[start + 1u].child;
|
|
142
|
+
return SE_SLOT_EMPTY;
|
|
143
|
+
}
|
|
144
|
+
|
|
106
145
|
if (count <= 8) {
|
|
107
146
|
for (uint32_t i = 0; i < count; i++) {
|
|
108
147
|
const se_trie_edge_t *edge = &trie->edges[start + i];
|
|
@@ -342,6 +381,37 @@ static se_status_t validate_norm_ranges(const se_range_t *ranges, uint32_t count
|
|
|
342
381
|
return SE_OK;
|
|
343
382
|
}
|
|
344
383
|
|
|
384
|
+
static void fill_map256(const se_map_entry_t **out, const se_map_entry_t *entries, uint32_t count) {
|
|
385
|
+
memset(out, 0, 256 * sizeof(*out));
|
|
386
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
387
|
+
if (entries[i].cp < 256)
|
|
388
|
+
out[entries[i].cp] = &entries[i];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
static void fill_range256(uint8_t *out, const se_range_t *ranges, uint32_t count) {
|
|
393
|
+
memset(out, 0, 256);
|
|
394
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
395
|
+
uint32_t lo = ranges[i].lo;
|
|
396
|
+
uint32_t hi = ranges[i].hi;
|
|
397
|
+
if (lo > 255)
|
|
398
|
+
continue;
|
|
399
|
+
if (hi > 255)
|
|
400
|
+
hi = 255;
|
|
401
|
+
for (uint32_t cp = lo; cp <= hi; cp++)
|
|
402
|
+
out[cp] = 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
static void prepare_norm_fast_tables(se_model_t *model) {
|
|
407
|
+
fill_map256(model->norm.lower256, model->norm.lower, model->norm.lower_count);
|
|
408
|
+
fill_map256(model->norm.nfd256, model->norm.nfd, model->norm.nfd_count);
|
|
409
|
+
fill_range256(model->norm.mn256, model->norm.mn, model->norm.mn_count);
|
|
410
|
+
fill_range256(model->norm.punct256, model->norm.punct, model->norm.punct_count);
|
|
411
|
+
fill_range256(model->norm.control256, model->norm.control, model->norm.control_count);
|
|
412
|
+
fill_range256(model->norm.whitespace256, model->norm.whitespace, model->norm.whitespace_count);
|
|
413
|
+
}
|
|
414
|
+
|
|
345
415
|
static se_status_t parse_norm_tables(se_model_t *model, const uint8_t *base, struct section sec,
|
|
346
416
|
se_error_t *err) {
|
|
347
417
|
memset(&model->norm, 0, sizeof(model->norm));
|
|
@@ -417,8 +487,13 @@ static se_status_t parse_norm_tables(se_model_t *model, const uint8_t *base, str
|
|
|
417
487
|
rc = validate_norm_ranges(model->norm.control, model->norm.control_count, "control", err);
|
|
418
488
|
if (rc != SE_OK)
|
|
419
489
|
return rc;
|
|
420
|
-
|
|
421
|
-
|
|
490
|
+
rc = validate_norm_ranges(model->norm.whitespace, model->norm.whitespace_count, "whitespace",
|
|
491
|
+
err);
|
|
492
|
+
if (rc != SE_OK)
|
|
493
|
+
return rc;
|
|
494
|
+
|
|
495
|
+
prepare_norm_fast_tables(model);
|
|
496
|
+
return SE_OK;
|
|
422
497
|
}
|
|
423
498
|
|
|
424
499
|
static se_status_t validate_vocab_hash(const se_model_t *model, struct section vocab_strings,
|
|
@@ -136,6 +136,12 @@ typedef struct {
|
|
|
136
136
|
uint32_t control_count;
|
|
137
137
|
const se_range_t *whitespace;
|
|
138
138
|
uint32_t whitespace_count;
|
|
139
|
+
const se_map_entry_t *lower256[256];
|
|
140
|
+
const se_map_entry_t *nfd256[256];
|
|
141
|
+
uint8_t mn256[256];
|
|
142
|
+
uint8_t punct256[256];
|
|
143
|
+
uint8_t control256[256];
|
|
144
|
+
uint8_t whitespace256[256];
|
|
139
145
|
} se_norm_tables_t;
|
|
140
146
|
|
|
141
147
|
typedef struct {
|
|
@@ -382,6 +388,9 @@ static inline int se_is_ascii_boundary(uint32_t cp) {
|
|
|
382
388
|
void se_scratch_init(se_scratch_t *s);
|
|
383
389
|
void se_scratch_free(se_scratch_t *s);
|
|
384
390
|
int se_scratch_reserve(se_scratch_t *s, uint32_t dim);
|
|
391
|
+
se_scratch_t *se_scratch_acquire(uint32_t dim);
|
|
392
|
+
void se_scratch_release(se_scratch_t *s);
|
|
393
|
+
void se_scratch_drop_thread(void);
|
|
385
394
|
|
|
386
395
|
size_t se_prefix_boundary_len(const se_model_t *model, const uint8_t *input, size_t input_len,
|
|
387
396
|
size_t target, size_t backscan);
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
#include <stdlib.h>
|
|
5
5
|
#include <string.h>
|
|
6
6
|
|
|
7
|
+
#ifndef _WIN32
|
|
8
|
+
#include <pthread.h>
|
|
9
|
+
#else
|
|
10
|
+
#ifndef WIN32_LEAN_AND_MEAN
|
|
11
|
+
#define WIN32_LEAN_AND_MEAN
|
|
12
|
+
#endif
|
|
13
|
+
#ifndef _WIN32_WINNT
|
|
14
|
+
#define _WIN32_WINNT 0x0600
|
|
15
|
+
#endif
|
|
16
|
+
#include <windows.h>
|
|
17
|
+
#endif
|
|
18
|
+
|
|
7
19
|
#define SE_CANCEL_CHECK_MASK 0x3ffu
|
|
8
20
|
|
|
9
21
|
void se_scratch_init(se_scratch_t *s) {
|
|
@@ -75,14 +87,18 @@ static int push_id(se_scratch_t *sc, size_t *n_ids, uint32_t id) {
|
|
|
75
87
|
return 1;
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
#define SE_SCRATCH_CPS_KEEP 256
|
|
91
|
+
#define SE_SCRATCH_IDS_KEEP 512
|
|
92
|
+
#define SE_SCRATCH_BYTES_KEEP 1024
|
|
93
|
+
|
|
78
94
|
int se_scratch_reserve(se_scratch_t *s, uint32_t dim) {
|
|
79
|
-
if (!grow_u32(&s->cps, &s->cps_cap,
|
|
95
|
+
if (!grow_u32(&s->cps, &s->cps_cap, SE_SCRATCH_CPS_KEEP))
|
|
80
96
|
return 0;
|
|
81
|
-
if (!grow_u32(&s->cps2, &s->cps2_cap,
|
|
97
|
+
if (!grow_u32(&s->cps2, &s->cps2_cap, SE_SCRATCH_CPS_KEEP))
|
|
82
98
|
return 0;
|
|
83
|
-
if (!grow_u32(&s->ids, &s->ids_cap,
|
|
99
|
+
if (!grow_u32(&s->ids, &s->ids_cap, SE_SCRATCH_IDS_KEEP))
|
|
84
100
|
return 0;
|
|
85
|
-
if (!grow_bytes(&s->bytes, &s->bytes_cap,
|
|
101
|
+
if (!grow_bytes(&s->bytes, &s->bytes_cap, SE_SCRATCH_BYTES_KEEP))
|
|
86
102
|
return 0;
|
|
87
103
|
if (!grow_float(&s->acc, &s->acc_cap, dim))
|
|
88
104
|
return 0;
|
|
@@ -90,21 +106,223 @@ int se_scratch_reserve(se_scratch_t *s, uint32_t dim) {
|
|
|
90
106
|
return 1;
|
|
91
107
|
}
|
|
92
108
|
|
|
109
|
+
static int shrink_u32(uint32_t **buf, size_t *cap, size_t keep) {
|
|
110
|
+
size_t bytes = 0;
|
|
111
|
+
void *p;
|
|
112
|
+
|
|
113
|
+
if (*cap <= keep)
|
|
114
|
+
return 1;
|
|
115
|
+
if (!*buf) {
|
|
116
|
+
*cap = 0;
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
if (!se_array_bytes(keep, sizeof(uint32_t), &bytes))
|
|
120
|
+
return 0;
|
|
121
|
+
p = se_realloc(SE_ALLOC_SCRATCH, *buf, bytes);
|
|
122
|
+
if (!p)
|
|
123
|
+
return 0;
|
|
124
|
+
*buf = (uint32_t *)p;
|
|
125
|
+
*cap = keep;
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static int shrink_bytes(uint8_t **buf, size_t *cap, size_t keep) {
|
|
130
|
+
size_t bytes = 0;
|
|
131
|
+
void *p;
|
|
132
|
+
|
|
133
|
+
if (*cap <= keep)
|
|
134
|
+
return 1;
|
|
135
|
+
if (!*buf) {
|
|
136
|
+
*cap = 0;
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
if (!se_array_bytes(keep, sizeof(uint8_t), &bytes))
|
|
140
|
+
return 0;
|
|
141
|
+
p = se_realloc(SE_ALLOC_SCRATCH, *buf, bytes);
|
|
142
|
+
if (!p)
|
|
143
|
+
return 0;
|
|
144
|
+
*buf = (uint8_t *)p;
|
|
145
|
+
*cap = keep;
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static void se_scratch_trim(se_scratch_t *s) {
|
|
150
|
+
(void)shrink_u32(&s->cps, &s->cps_cap, SE_SCRATCH_CPS_KEEP);
|
|
151
|
+
(void)shrink_u32(&s->cps2, &s->cps2_cap, SE_SCRATCH_CPS_KEEP);
|
|
152
|
+
(void)shrink_u32(&s->ids, &s->ids_cap, SE_SCRATCH_IDS_KEEP);
|
|
153
|
+
(void)shrink_bytes(&s->bytes, &s->bytes_cap, SE_SCRATCH_BYTES_KEEP);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
typedef struct {
|
|
157
|
+
se_scratch_t scratch;
|
|
158
|
+
int in_use;
|
|
159
|
+
} se_tls_scratch_t;
|
|
160
|
+
|
|
161
|
+
static void se_scratch_tls_dtor(void *p) {
|
|
162
|
+
se_tls_scratch_t *tls = (se_tls_scratch_t *)p;
|
|
163
|
+
if (!tls)
|
|
164
|
+
return;
|
|
165
|
+
se_scratch_free(&tls->scratch);
|
|
166
|
+
se_free(tls);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#ifndef _WIN32
|
|
170
|
+
static pthread_key_t se_scratch_key;
|
|
171
|
+
static pthread_once_t se_scratch_once = PTHREAD_ONCE_INIT;
|
|
172
|
+
|
|
173
|
+
static void se_scratch_key_init(void) {
|
|
174
|
+
(void)pthread_key_create(&se_scratch_key, se_scratch_tls_dtor);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static se_tls_scratch_t *se_scratch_tls_get(void) {
|
|
178
|
+
(void)pthread_once(&se_scratch_once, se_scratch_key_init);
|
|
179
|
+
return (se_tls_scratch_t *)pthread_getspecific(se_scratch_key);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static se_tls_scratch_t *se_scratch_tls_slot(void) {
|
|
183
|
+
se_tls_scratch_t *tls = se_scratch_tls_get();
|
|
184
|
+
if (tls)
|
|
185
|
+
return tls;
|
|
186
|
+
|
|
187
|
+
tls = (se_tls_scratch_t *)se_malloc(SE_ALLOC_SCRATCH, sizeof(*tls));
|
|
188
|
+
if (!tls)
|
|
189
|
+
return NULL;
|
|
190
|
+
memset(tls, 0, sizeof(*tls));
|
|
191
|
+
se_scratch_init(&tls->scratch);
|
|
192
|
+
if (pthread_setspecific(se_scratch_key, tls) != 0) {
|
|
193
|
+
se_free(tls);
|
|
194
|
+
return NULL;
|
|
195
|
+
}
|
|
196
|
+
return tls;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static void se_scratch_tls_clear(void) {
|
|
200
|
+
se_tls_scratch_t *tls = se_scratch_tls_get();
|
|
201
|
+
if (!tls)
|
|
202
|
+
return;
|
|
203
|
+
(void)pthread_setspecific(se_scratch_key, NULL);
|
|
204
|
+
se_scratch_tls_dtor(tls);
|
|
205
|
+
}
|
|
206
|
+
#else
|
|
207
|
+
static DWORD se_fls_index = FLS_OUT_OF_INDEXES;
|
|
208
|
+
static INIT_ONCE se_fls_once = INIT_ONCE_STATIC_INIT;
|
|
209
|
+
|
|
210
|
+
static VOID WINAPI se_scratch_fls_dtor(PVOID p) {
|
|
211
|
+
se_scratch_tls_dtor(p);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
static BOOL CALLBACK se_scratch_fls_init(PINIT_ONCE once, PVOID param, PVOID *ctx) {
|
|
215
|
+
(void)once;
|
|
216
|
+
(void)param;
|
|
217
|
+
(void)ctx;
|
|
218
|
+
se_fls_index = FlsAlloc(se_scratch_fls_dtor);
|
|
219
|
+
return se_fls_index != FLS_OUT_OF_INDEXES;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static se_tls_scratch_t *se_scratch_tls_get(void) {
|
|
223
|
+
if (se_fls_index == FLS_OUT_OF_INDEXES)
|
|
224
|
+
return NULL;
|
|
225
|
+
return (se_tls_scratch_t *)FlsGetValue(se_fls_index);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static se_tls_scratch_t *se_scratch_tls_slot(void) {
|
|
229
|
+
if (!InitOnceExecuteOnce(&se_fls_once, se_scratch_fls_init, NULL, NULL) ||
|
|
230
|
+
se_fls_index == FLS_OUT_OF_INDEXES)
|
|
231
|
+
return NULL;
|
|
232
|
+
|
|
233
|
+
se_tls_scratch_t *tls = se_scratch_tls_get();
|
|
234
|
+
if (tls)
|
|
235
|
+
return tls;
|
|
236
|
+
|
|
237
|
+
tls = (se_tls_scratch_t *)se_malloc(SE_ALLOC_SCRATCH, sizeof(*tls));
|
|
238
|
+
if (!tls)
|
|
239
|
+
return NULL;
|
|
240
|
+
memset(tls, 0, sizeof(*tls));
|
|
241
|
+
se_scratch_init(&tls->scratch);
|
|
242
|
+
if (!FlsSetValue(se_fls_index, tls)) {
|
|
243
|
+
se_free(tls);
|
|
244
|
+
return NULL;
|
|
245
|
+
}
|
|
246
|
+
return tls;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static void se_scratch_tls_clear(void) {
|
|
250
|
+
se_tls_scratch_t *tls = se_scratch_tls_get();
|
|
251
|
+
if (!tls)
|
|
252
|
+
return;
|
|
253
|
+
(void)FlsSetValue(se_fls_index, NULL);
|
|
254
|
+
se_scratch_tls_dtor(tls);
|
|
255
|
+
}
|
|
256
|
+
#endif
|
|
257
|
+
|
|
258
|
+
se_scratch_t *se_scratch_acquire(uint32_t dim) {
|
|
259
|
+
se_tls_scratch_t *tls = se_scratch_tls_slot();
|
|
260
|
+
if (!tls)
|
|
261
|
+
return NULL;
|
|
262
|
+
|
|
263
|
+
if (!tls->in_use) {
|
|
264
|
+
if (!se_scratch_reserve(&tls->scratch, dim))
|
|
265
|
+
return NULL;
|
|
266
|
+
tls->in_use = 1;
|
|
267
|
+
return &tls->scratch;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
se_scratch_t *heap = (se_scratch_t *)se_malloc(SE_ALLOC_SCRATCH, sizeof(*heap));
|
|
271
|
+
if (!heap)
|
|
272
|
+
return NULL;
|
|
273
|
+
se_scratch_init(heap);
|
|
274
|
+
if (!se_scratch_reserve(heap, dim)) {
|
|
275
|
+
se_scratch_free(heap);
|
|
276
|
+
se_free(heap);
|
|
277
|
+
return NULL;
|
|
278
|
+
}
|
|
279
|
+
return heap;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
void se_scratch_release(se_scratch_t *s) {
|
|
283
|
+
se_tls_scratch_t *tls;
|
|
284
|
+
|
|
285
|
+
if (!s)
|
|
286
|
+
return;
|
|
287
|
+
|
|
288
|
+
tls = se_scratch_tls_get();
|
|
289
|
+
if (tls && s == &tls->scratch) {
|
|
290
|
+
tls->in_use = 0;
|
|
291
|
+
se_scratch_trim(&tls->scratch);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
se_scratch_free(s);
|
|
295
|
+
se_free(s);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void se_scratch_drop_thread(void) {
|
|
299
|
+
se_tls_scratch_t *tls = se_scratch_tls_get();
|
|
300
|
+
if (tls && tls->in_use)
|
|
301
|
+
tls->in_use = 0;
|
|
302
|
+
se_scratch_tls_clear();
|
|
303
|
+
}
|
|
304
|
+
|
|
93
305
|
static int is_whitespace(const se_model_t *m, uint32_t cp) {
|
|
94
306
|
if (cp < 0x80)
|
|
95
307
|
return se_is_ascii_whitespace(cp);
|
|
308
|
+
if (cp < 256)
|
|
309
|
+
return m->norm.whitespace256[cp];
|
|
96
310
|
return se_range_contains(m->norm.whitespace, m->norm.whitespace_count, cp);
|
|
97
311
|
}
|
|
98
312
|
|
|
99
313
|
static int is_control(const se_model_t *m, uint32_t cp) {
|
|
100
314
|
if (cp < 0x80)
|
|
101
315
|
return (cp < 0x20 || cp == 0x7f) && cp != '\t' && cp != '\n' && cp != '\r';
|
|
316
|
+
if (cp < 256)
|
|
317
|
+
return m->norm.control256[cp];
|
|
102
318
|
return se_range_contains(m->norm.control, m->norm.control_count, cp);
|
|
103
319
|
}
|
|
104
320
|
|
|
105
321
|
static int is_punct(const se_model_t *m, uint32_t cp) {
|
|
106
322
|
if (cp < 0x80)
|
|
107
323
|
return se_is_ascii_punct(cp);
|
|
324
|
+
if (cp < 256)
|
|
325
|
+
return m->norm.punct256[cp];
|
|
108
326
|
return se_range_contains(m->norm.punct, m->norm.punct_count, cp);
|
|
109
327
|
}
|
|
110
328
|
|
|
@@ -154,12 +372,18 @@ static int decode_one(const uint8_t *src, size_t len, size_t *i, uint32_t *cp_ou
|
|
|
154
372
|
static int normalization_stable(const se_model_t *m, uint32_t cp) {
|
|
155
373
|
if (cp < 0x80)
|
|
156
374
|
return 1;
|
|
157
|
-
if (m->meta.do_lower_case
|
|
158
|
-
|
|
375
|
+
if (m->meta.do_lower_case) {
|
|
376
|
+
const se_map_entry_t *lower =
|
|
377
|
+
cp < 256 ? m->norm.lower256[cp] : se_map_lookup(m->norm.lower, m->norm.lower_count, cp);
|
|
378
|
+
if (lower)
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
159
381
|
if (m->meta.strip_accents) {
|
|
160
|
-
|
|
382
|
+
const se_map_entry_t *nfd =
|
|
383
|
+
cp < 256 ? m->norm.nfd256[cp] : se_map_lookup(m->norm.nfd, m->norm.nfd_count, cp);
|
|
384
|
+
if (nfd)
|
|
161
385
|
return 0;
|
|
162
|
-
if (se_range_contains(m->norm.mn, m->norm.mn_count, cp))
|
|
386
|
+
if (cp < 256 ? m->norm.mn256[cp] : se_range_contains(m->norm.mn, m->norm.mn_count, cp))
|
|
163
387
|
return 0;
|
|
164
388
|
}
|
|
165
389
|
return 1;
|
|
@@ -233,6 +457,7 @@ typedef struct {
|
|
|
233
457
|
size_t n_ids;
|
|
234
458
|
size_t n_unk;
|
|
235
459
|
size_t segment_len;
|
|
460
|
+
int segment_ascii;
|
|
236
461
|
se_token_stats_t *stats;
|
|
237
462
|
se_error_t *err;
|
|
238
463
|
volatile sig_atomic_t *cancelled;
|
|
@@ -248,6 +473,8 @@ static se_status_t oom(token_state_t *st, const char *where) {
|
|
|
248
473
|
}
|
|
249
474
|
|
|
250
475
|
static int append_segment_cp(token_state_t *st, uint32_t cp) {
|
|
476
|
+
if (cp >= 0x80)
|
|
477
|
+
st->segment_ascii = 0;
|
|
251
478
|
if (!grow_u32(&st->scratch->cps, &st->scratch->cps_cap, st->segment_len + 1))
|
|
252
479
|
return 0;
|
|
253
480
|
st->scratch->cps[st->segment_len++] = cp;
|
|
@@ -272,7 +499,8 @@ static int cap_after_append(token_state_t *st) {
|
|
|
272
499
|
typedef enum { WORDPIECE_OK = 1, WORDPIECE_OOM = 0, WORDPIECE_INVALID = -1 } wordpiece_status_t;
|
|
273
500
|
|
|
274
501
|
static wordpiece_status_t wordpiece(const se_model_t *m, se_scratch_t *sc, const uint32_t *word,
|
|
275
|
-
size_t word_len, size_t *n_ids, size_t *n_unk
|
|
502
|
+
size_t word_len, size_t *n_ids, size_t *n_unk,
|
|
503
|
+
int known_ascii) {
|
|
276
504
|
const se_meta_t *meta = &m->meta;
|
|
277
505
|
|
|
278
506
|
if (word_len == 0)
|
|
@@ -285,30 +513,49 @@ static wordpiece_status_t wordpiece(const se_model_t *m, se_scratch_t *sc, const
|
|
|
285
513
|
return 1;
|
|
286
514
|
}
|
|
287
515
|
|
|
288
|
-
|
|
289
|
-
if (!
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
516
|
+
int ascii_identity = known_ascii;
|
|
517
|
+
if (!ascii_identity) {
|
|
518
|
+
ascii_identity = 1;
|
|
519
|
+
for (size_t k = 0; k < word_len; k++) {
|
|
520
|
+
if (word[k] >= 0x80) {
|
|
521
|
+
ascii_identity = 0;
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
296
526
|
|
|
297
527
|
size_t blen = 0;
|
|
298
|
-
|
|
299
|
-
if (
|
|
528
|
+
if (ascii_identity) {
|
|
529
|
+
if (!grow_bytes(&sc->bytes, &sc->bytes_cap, word_len))
|
|
530
|
+
return 0;
|
|
531
|
+
if (!grow_u32(&sc->cps2, &sc->cps2_cap, word_len + 1))
|
|
300
532
|
return 0;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
sc->bytes[blen++] = (uint8_t)cp;
|
|
305
|
-
} else {
|
|
306
|
-
blen += se_utf8_encode(cp, sc->bytes + blen);
|
|
533
|
+
for (size_t k = 0; k < word_len; k++) {
|
|
534
|
+
sc->bytes[k] = (uint8_t)word[k];
|
|
535
|
+
sc->cps2[k] = (uint32_t)k;
|
|
307
536
|
}
|
|
537
|
+
blen = word_len;
|
|
538
|
+
sc->cps2[word_len] = (uint32_t)word_len;
|
|
539
|
+
} else {
|
|
540
|
+
size_t bytes_need = 0;
|
|
541
|
+
if (!se_checked_mul_size(word_len, 4, &bytes_need) ||
|
|
542
|
+
!se_checked_add_size(bytes_need, 4, &bytes_need))
|
|
543
|
+
return 0;
|
|
544
|
+
if (!grow_bytes(&sc->bytes, &sc->bytes_cap, bytes_need))
|
|
545
|
+
return 0;
|
|
546
|
+
if (!grow_u32(&sc->cps2, &sc->cps2_cap, word_len + 1))
|
|
547
|
+
return 0;
|
|
548
|
+
|
|
549
|
+
for (size_t k = 0; k < word_len; k++) {
|
|
550
|
+
if (blen > UINT32_MAX)
|
|
551
|
+
return 0;
|
|
552
|
+
sc->cps2[k] = (uint32_t)blen;
|
|
553
|
+
blen += se_utf8_encode(word[k], sc->bytes + blen);
|
|
554
|
+
}
|
|
555
|
+
if (blen > UINT32_MAX)
|
|
556
|
+
return 0;
|
|
557
|
+
sc->cps2[word_len] = (uint32_t)blen;
|
|
308
558
|
}
|
|
309
|
-
if (blen > UINT32_MAX)
|
|
310
|
-
return 0;
|
|
311
|
-
sc->cps2[word_len] = (uint32_t)blen;
|
|
312
559
|
|
|
313
560
|
if (word_len <= meta->max_token_chars) {
|
|
314
561
|
uint32_t exact_id = 0;
|
|
@@ -358,7 +605,8 @@ static wordpiece_status_t wordpiece(const se_model_t *m, se_scratch_t *sc, const
|
|
|
358
605
|
static se_status_t append_wordpiece(token_state_t *st, const uint32_t *word, size_t word_len,
|
|
359
606
|
int *stop) {
|
|
360
607
|
wordpiece_status_t wp =
|
|
361
|
-
wordpiece(st->model, st->scratch, word, word_len, &st->n_ids, &st->n_unk
|
|
608
|
+
wordpiece(st->model, st->scratch, word, word_len, &st->n_ids, &st->n_unk,
|
|
609
|
+
word_len == 1 ? (word[0] < 0x80) : st->segment_ascii);
|
|
362
610
|
if (wp == WORDPIECE_OOM)
|
|
363
611
|
return oom(st, "tokenizing");
|
|
364
612
|
if (wp == WORDPIECE_INVALID) {
|
|
@@ -375,6 +623,7 @@ static se_status_t flush_segment(token_state_t *st, int *stop) {
|
|
|
375
623
|
return SE_OK;
|
|
376
624
|
se_status_t rc = append_wordpiece(st, st->scratch->cps, st->segment_len, stop);
|
|
377
625
|
st->segment_len = 0;
|
|
626
|
+
st->segment_ascii = 1;
|
|
378
627
|
return rc;
|
|
379
628
|
}
|
|
380
629
|
|
|
@@ -394,13 +643,30 @@ static se_status_t feed_token_cp(token_state_t *st, uint32_t cp, int *stop) {
|
|
|
394
643
|
return SE_OK;
|
|
395
644
|
}
|
|
396
645
|
|
|
646
|
+
static const se_map_entry_t *lower_entry(const se_model_t *m, uint32_t cp) {
|
|
647
|
+
if (cp < 256)
|
|
648
|
+
return m->norm.lower256[cp];
|
|
649
|
+
return se_map_lookup(m->norm.lower, m->norm.lower_count, cp);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
static const se_map_entry_t *nfd_entry(const se_model_t *m, uint32_t cp) {
|
|
653
|
+
if (cp < 256)
|
|
654
|
+
return m->norm.nfd256[cp];
|
|
655
|
+
return se_map_lookup(m->norm.nfd, m->norm.nfd_count, cp);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static int is_mn(const se_model_t *m, uint32_t cp) {
|
|
659
|
+
if (cp < 256)
|
|
660
|
+
return m->norm.mn256[cp];
|
|
661
|
+
return se_range_contains(m->norm.mn, m->norm.mn_count, cp);
|
|
662
|
+
}
|
|
663
|
+
|
|
397
664
|
static se_status_t emit_lowered(token_state_t *st, uint32_t cp, int *stop) {
|
|
398
665
|
if (st->model->meta.do_lower_case) {
|
|
399
666
|
if (cp >= 'A' && cp <= 'Z')
|
|
400
667
|
cp += 32;
|
|
401
668
|
else if (cp >= 0x80) {
|
|
402
|
-
const se_map_entry_t *e =
|
|
403
|
-
se_map_lookup(st->model->norm.lower, st->model->norm.lower_count, cp);
|
|
669
|
+
const se_map_entry_t *e = lower_entry(st->model, cp);
|
|
404
670
|
if (e) {
|
|
405
671
|
for (uint32_t k = 0; k < e->len; k++) {
|
|
406
672
|
se_status_t rc = feed_token_cp(st, e->out[k], stop);
|
|
@@ -418,16 +684,16 @@ static se_status_t emit_stripped(token_state_t *st, uint32_t cp, int *stop) {
|
|
|
418
684
|
if (!st->model->meta.strip_accents || cp < 0x80)
|
|
419
685
|
return emit_lowered(st, cp, stop);
|
|
420
686
|
|
|
421
|
-
const se_map_entry_t *e =
|
|
687
|
+
const se_map_entry_t *e = nfd_entry(st->model, cp);
|
|
422
688
|
if (!e) {
|
|
423
|
-
if (
|
|
689
|
+
if (is_mn(st->model, cp))
|
|
424
690
|
return SE_OK;
|
|
425
691
|
return emit_lowered(st, cp, stop);
|
|
426
692
|
}
|
|
427
693
|
|
|
428
694
|
for (uint32_t k = 0; k < e->len; k++) {
|
|
429
695
|
uint32_t d = e->out[k];
|
|
430
|
-
if (
|
|
696
|
+
if (is_mn(st->model, d))
|
|
431
697
|
continue;
|
|
432
698
|
se_status_t rc = emit_lowered(st, d, stop);
|
|
433
699
|
if (rc != SE_OK || *stop)
|
|
@@ -557,6 +823,7 @@ se_status_t se_tokenize(const se_model_t *model, se_scratch_t *sc, const uint8_t
|
|
|
557
823
|
st.stats = stats;
|
|
558
824
|
st.err = err;
|
|
559
825
|
st.cancelled = cancelled;
|
|
826
|
+
st.segment_ascii = 1;
|
|
560
827
|
|
|
561
828
|
size_t i = 0;
|
|
562
829
|
size_t iterations = 0;
|
|
@@ -274,10 +274,14 @@ static void batch_worker_run(batch_job_t *job, se_scratch_t *scratch) {
|
|
|
274
274
|
|
|
275
275
|
static void *batch_execute(void *arg) {
|
|
276
276
|
batch_job_t *job = (batch_job_t *)arg;
|
|
277
|
-
se_scratch_t scratch;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
se_scratch_t *scratch = se_scratch_acquire(job->model->meta.dim);
|
|
278
|
+
if (!scratch) {
|
|
279
|
+
se_error_set(&job->error, SE_ERR_OOM, "out of memory while sizing scratch buffers");
|
|
280
|
+
job->failed = 1;
|
|
281
|
+
return NULL;
|
|
282
|
+
}
|
|
283
|
+
batch_worker_run(job, scratch);
|
|
284
|
+
se_scratch_release(scratch);
|
|
281
285
|
return NULL;
|
|
282
286
|
}
|
|
283
287
|
|
|
@@ -928,6 +932,32 @@ static VALUE embed_one_via_batch(VALUE self, VALUE text, VALUE max_tokens_opt,
|
|
|
928
932
|
return embed_texts_internal(self, text, 0, 1, max_tokens_opt, format, validation, stats);
|
|
929
933
|
}
|
|
930
934
|
|
|
935
|
+
typedef struct {
|
|
936
|
+
se_scratch_t *scratch;
|
|
937
|
+
const se_model_t *model;
|
|
938
|
+
const uint8_t *input;
|
|
939
|
+
size_t input_len;
|
|
940
|
+
uint32_t max_tokens;
|
|
941
|
+
float *out;
|
|
942
|
+
se_token_stats_t *stats;
|
|
943
|
+
se_error_t err;
|
|
944
|
+
se_status_t rc;
|
|
945
|
+
} embed_one_scratch_job_t;
|
|
946
|
+
|
|
947
|
+
static VALUE embed_one_scratch_body(VALUE arg) {
|
|
948
|
+
embed_one_scratch_job_t *job = (embed_one_scratch_job_t *)(uintptr_t)arg;
|
|
949
|
+
job->rc = se_embed_one(job->model, job->scratch, job->input, job->input_len, job->max_tokens,
|
|
950
|
+
job->out, job->stats, &job->err, NULL);
|
|
951
|
+
return Qnil;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
static VALUE embed_one_scratch_ensure(VALUE arg) {
|
|
955
|
+
embed_one_scratch_job_t *job = (embed_one_scratch_job_t *)(uintptr_t)arg;
|
|
956
|
+
se_scratch_release(job->scratch);
|
|
957
|
+
job->scratch = NULL;
|
|
958
|
+
return Qnil;
|
|
959
|
+
}
|
|
960
|
+
|
|
931
961
|
static VALUE embed_one_value(VALUE self, VALUE text, VALUE max_tokens_opt,
|
|
932
962
|
se_vector_format_t format, se_encoding_validation_t validation,
|
|
933
963
|
se_token_stats_t *stats) {
|
|
@@ -947,25 +977,28 @@ static VALUE embed_one_value(VALUE self, VALUE text, VALUE max_tokens_opt,
|
|
|
947
977
|
rb_enc_associate(result, binary_encoding);
|
|
948
978
|
float *out = (float *)RSTRING_PTR(result);
|
|
949
979
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
980
|
+
se_token_stats_t local_stats;
|
|
981
|
+
embed_one_scratch_job_t job;
|
|
982
|
+
memset(&job, 0, sizeof(job));
|
|
983
|
+
job.model = &w->model;
|
|
984
|
+
job.input = (const uint8_t *)RSTRING_PTR(text);
|
|
985
|
+
job.input_len = (size_t)RSTRING_LEN(text);
|
|
986
|
+
job.max_tokens = resolve_max_tokens(&w->model, max_tokens_opt);
|
|
987
|
+
job.out = out;
|
|
988
|
+
job.stats = stats ? stats : &local_stats;
|
|
989
|
+
se_error_clear(&job.err);
|
|
990
|
+
|
|
991
|
+
job.scratch = se_scratch_acquire(dim);
|
|
992
|
+
if (!job.scratch)
|
|
954
993
|
rb_raise(rb_eNoMemError, "out of memory");
|
|
955
|
-
}
|
|
956
994
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
se_token_stats_t local_stats;
|
|
960
|
-
se_status_t rc =
|
|
961
|
-
se_embed_one(&w->model, &scratch, (const uint8_t *)RSTRING_PTR(text),
|
|
962
|
-
(size_t)RSTRING_LEN(text), resolve_max_tokens(&w->model, max_tokens_opt), out,
|
|
963
|
-
stats ? stats : &local_stats, &err, NULL);
|
|
964
|
-
se_scratch_free(&scratch);
|
|
995
|
+
rb_ensure(embed_one_scratch_body, (VALUE)(uintptr_t)&job, embed_one_scratch_ensure,
|
|
996
|
+
(VALUE)(uintptr_t)&job);
|
|
965
997
|
RB_GC_GUARD(text);
|
|
998
|
+
RB_GC_GUARD(result);
|
|
966
999
|
|
|
967
|
-
if (rc != SE_OK)
|
|
968
|
-
raise_se(&err);
|
|
1000
|
+
if (job.rc != SE_OK)
|
|
1001
|
+
raise_se(&job.err);
|
|
969
1002
|
|
|
970
1003
|
return result;
|
|
971
1004
|
}
|
|
@@ -1001,6 +1034,38 @@ static VALUE model_embed_with_stats(int argc, VALUE *argv, VALUE self) {
|
|
|
1001
1034
|
return hash;
|
|
1002
1035
|
}
|
|
1003
1036
|
|
|
1037
|
+
typedef struct {
|
|
1038
|
+
se_scratch_t *scratch;
|
|
1039
|
+
const se_model_t *model;
|
|
1040
|
+
const uint8_t *input;
|
|
1041
|
+
size_t input_len;
|
|
1042
|
+
uint32_t max_tokens;
|
|
1043
|
+
se_token_stats_t stats;
|
|
1044
|
+
se_error_t err;
|
|
1045
|
+
se_status_t rc;
|
|
1046
|
+
VALUE ids;
|
|
1047
|
+
} tokenize_scratch_job_t;
|
|
1048
|
+
|
|
1049
|
+
static VALUE tokenize_scratch_body(VALUE arg) {
|
|
1050
|
+
tokenize_scratch_job_t *job = (tokenize_scratch_job_t *)(uintptr_t)arg;
|
|
1051
|
+
job->rc = se_tokenize(job->model, job->scratch, job->input, job->input_len, job->max_tokens,
|
|
1052
|
+
&job->stats, &job->err, NULL);
|
|
1053
|
+
if (job->rc != SE_OK)
|
|
1054
|
+
return Qnil;
|
|
1055
|
+
|
|
1056
|
+
job->ids = rb_ary_new_capa((long)job->stats.token_count);
|
|
1057
|
+
for (uint32_t i = 0; i < job->stats.token_count; i++)
|
|
1058
|
+
rb_ary_push(job->ids, UINT2NUM(job->scratch->ids[i]));
|
|
1059
|
+
return Qnil;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
static VALUE tokenize_scratch_ensure(VALUE arg) {
|
|
1063
|
+
tokenize_scratch_job_t *job = (tokenize_scratch_job_t *)(uintptr_t)arg;
|
|
1064
|
+
se_scratch_release(job->scratch);
|
|
1065
|
+
job->scratch = NULL;
|
|
1066
|
+
return Qnil;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1004
1069
|
static VALUE model_tokenize(int argc, VALUE *argv, VALUE self) {
|
|
1005
1070
|
VALUE text, opts;
|
|
1006
1071
|
rb_scan_args(argc, argv, "1:", &text, &opts);
|
|
@@ -1011,32 +1076,27 @@ static VALUE model_tokenize(int argc, VALUE *argv, VALUE self) {
|
|
|
1011
1076
|
check_text_encoding_mode(
|
|
1012
1077
|
text, -1, resolve_encoding_validation(lookup_option(opts, id_validate_encoding)));
|
|
1013
1078
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1079
|
+
tokenize_scratch_job_t job;
|
|
1080
|
+
memset(&job, 0, sizeof(job));
|
|
1081
|
+
job.model = &w->model;
|
|
1082
|
+
job.input = (const uint8_t *)RSTRING_PTR(text);
|
|
1083
|
+
job.input_len = (size_t)RSTRING_LEN(text);
|
|
1084
|
+
job.max_tokens = resolve_max_tokens(&w->model, lookup_option(opts, id_max_tokens));
|
|
1085
|
+
job.ids = Qnil;
|
|
1086
|
+
se_error_clear(&job.err);
|
|
1087
|
+
|
|
1088
|
+
job.scratch = se_scratch_acquire(w->model.meta.dim);
|
|
1089
|
+
if (!job.scratch)
|
|
1020
1090
|
rb_raise(rb_eNoMemError, "out of memory");
|
|
1021
|
-
}
|
|
1022
1091
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
se_status_t rc = se_tokenize(&w->model, &scratch, (const uint8_t *)RSTRING_PTR(text),
|
|
1027
|
-
(size_t)RSTRING_LEN(text), max_tokens, &stats, &err, NULL);
|
|
1028
|
-
if (rc != SE_OK) {
|
|
1029
|
-
se_scratch_free(&scratch);
|
|
1030
|
-
raise_se(&err);
|
|
1031
|
-
}
|
|
1092
|
+
rb_ensure(tokenize_scratch_body, (VALUE)(uintptr_t)&job, tokenize_scratch_ensure,
|
|
1093
|
+
(VALUE)(uintptr_t)&job);
|
|
1094
|
+
RB_GC_GUARD(text);
|
|
1032
1095
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
rb_ary_push(ids, UINT2NUM(scratch.ids[i]));
|
|
1096
|
+
if (job.rc != SE_OK)
|
|
1097
|
+
raise_se(&job.err);
|
|
1036
1098
|
|
|
1037
|
-
|
|
1038
|
-
RB_GC_GUARD(text);
|
|
1039
|
-
return ids;
|
|
1099
|
+
return job.ids;
|
|
1040
1100
|
}
|
|
1041
1101
|
|
|
1042
1102
|
typedef struct {
|
|
@@ -1077,17 +1137,15 @@ static VALUE num2ull_at_value(VALUE arg) {
|
|
|
1077
1137
|
|
|
1078
1138
|
static void *ids_execute(void *arg) {
|
|
1079
1139
|
ids_job_t *job = (ids_job_t *)arg;
|
|
1080
|
-
se_scratch_t scratch;
|
|
1081
|
-
|
|
1082
|
-
if (!se_scratch_reserve(&scratch, job->model->meta.dim)) {
|
|
1140
|
+
se_scratch_t *scratch = se_scratch_acquire(job->model->meta.dim);
|
|
1141
|
+
if (!scratch) {
|
|
1083
1142
|
se_error_set(&job->error, SE_ERR_OOM, "out of memory while sizing scratch buffers");
|
|
1084
|
-
se_scratch_free(&scratch);
|
|
1085
1143
|
return NULL;
|
|
1086
1144
|
}
|
|
1087
1145
|
se_error_clear(&job->error);
|
|
1088
|
-
se_embed_ids(job->model,
|
|
1146
|
+
se_embed_ids(job->model, scratch, job->ids, job->n_ids, job->out, &job->stats, &job->error,
|
|
1089
1147
|
&job->cancelled);
|
|
1090
|
-
|
|
1148
|
+
se_scratch_release(scratch);
|
|
1091
1149
|
return NULL;
|
|
1092
1150
|
}
|
|
1093
1151
|
|