static_embeddings 0.1.1 → 0.1.2
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 +121 -0
- data/README.md +167 -276
- data/docs/ARCHITECTURE.md +56 -6
- data/docs/MODEL_AUDIT.md +21 -8
- data/docs/PERFORMANCE.md +104 -36
- data/ext/static_embeddings/extconf.rb +4 -0
- data/ext/static_embeddings/se_alloc_stats.c +244 -0
- data/ext/static_embeddings/se_f16.c +378 -0
- data/ext/static_embeddings/se_format.c +235 -148
- data/ext/static_embeddings/se_internal.h +159 -0
- data/ext/static_embeddings/se_tokenizer.c +144 -41
- data/ext/static_embeddings/se_topk.c +236 -0
- data/ext/static_embeddings/static_embeddings.c +225 -744
- data/lib/static_embeddings/format.rb +22 -11
- data/lib/static_embeddings/version.rb +1 -1
- data/tools/benchmark.rb +13 -4
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 250b847231ef37114e5c1c6b12076565eaa42d6193c305b0b107b424b12b3397
|
|
4
|
+
data.tar.gz: e59f63626b715759a286c25949b3c03bed78bc58744085716b96cd96a8fa8f98
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71f7a2e68300934fe1362c5b8f546312ced1424dc0616a752400d8d48c9d80a1b82e053592bcf882e24f3a0ba7a10044b7b17a23a2bb5ea6bbe6f8dfcb896e97
|
|
7
|
+
data.tar.gz: 957e006602a759274873d894cbaf40a50018af531a0a9cd39806ef5eea8f6159b59b4ebba3e4bc2f8daaf3fe6b89f3ed4eeb9e10d28a62eef1057a6e363226c1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,126 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (unreleased)
|
|
4
|
+
|
|
5
|
+
A tokenizer correctness fix, the ASCII fast path it made safe to write, and an
|
|
6
|
+
optional build that accounts for the runtime's own C heap.
|
|
7
|
+
|
|
8
|
+
External parity against `model2vec.StaticModel` has **not** been re-run for this
|
|
9
|
+
version. The DEL fix below changes token ids for any input containing `U+007F`,
|
|
10
|
+
so the 0.1.1 audit record no longer covers the shipping runtime. See
|
|
11
|
+
`docs/MODEL_AUDIT.md`; that re-run is the release blocker.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`U+007F DEL` was not treated as a control character.** `is_control()`
|
|
16
|
+
matched `cp < 0x20` only, so DEL survived `clean_text` and stayed a word byte
|
|
17
|
+
while the reference normalizer drops it. The effect was not one token: the
|
|
18
|
+
word carrying DEL fell out of the vocabulary and collapsed to `[UNK]`.
|
|
19
|
+
A 20 000-case differential fuzz against `StaticEmbeddings::Reference` produced
|
|
20
|
+
4 084 mismatches before the fix and 0 after; an exhaustive sweep of
|
|
21
|
+
`0x00..0x7F` shows DEL was the only divergent ASCII byte.
|
|
22
|
+
|
|
23
|
+
- **`embed_batch(format: :f16)` allocated a second output buffer.** The f32
|
|
24
|
+
batch buffer was encoded into a freshly allocated half buffer, so an f16 call
|
|
25
|
+
peaked at 1.5x the f32 output and cost more transient C heap than f32 for the
|
|
26
|
+
same work. It now encodes in place. Output is byte-identical; on 5000 x 512
|
|
27
|
+
the transient peak drops from 16.4 MB to 11.3 MB, the same peak the f32 call
|
|
28
|
+
has. Throughput is unchanged: the run-to-run spread on that sample is wider
|
|
29
|
+
than any difference the change makes.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **ASCII fast path in the tokenizer.** For `cp < 0x80` the normalizer chain
|
|
34
|
+
resolves the CJK, NFD, combining-mark and case-folding branches identically
|
|
35
|
+
every time, so ASCII runs now go through a 128-entry classification table and
|
|
36
|
+
reserve their codepoint buffer once per word instead of once per character.
|
|
37
|
+
Output is unchanged: the token-stream digest over 20 000 fuzz cases is
|
|
38
|
+
identical with and without it. Roughly 1.7x on `tokenize` and 1.2x on `embed`
|
|
39
|
+
for short English text, 2x on `tokenize` with `max_tokens: false`. Non-ASCII
|
|
40
|
+
input is unaffected.
|
|
41
|
+
|
|
42
|
+
- **`test/ascii_parity_test.rb`.** Exhaustive `0x00..0x7F` sweep in six
|
|
43
|
+
contexts, boundary codepoints across the ASCII/Unicode edge, deterministic
|
|
44
|
+
differential fuzz against `Reference` (`FUZZ_N`, `FUZZ_SEED`), truncation
|
|
45
|
+
boundaries, and DEL walked across the prefix window so the `embed` path is
|
|
46
|
+
covered and not just `tokenize`. Four of its cases fail on 0.1.1.
|
|
47
|
+
|
|
48
|
+
- **`test/validate_encoding_test.rb`** and verify coverage in
|
|
49
|
+
`test/format_test.rb`: mode agreement on valid input, invalid bytes inside
|
|
50
|
+
and past the window, cached-broken coderange, non-UTF-8 encodings, batch
|
|
51
|
+
index reporting, tamper detection, truncated and foreign files, and an
|
|
52
|
+
allocation bound proving `verify` does not hold the file in memory.
|
|
53
|
+
|
|
54
|
+
- **`test/cancellation_timing_test.rb`.** Measures how far past a `Timeout`
|
|
55
|
+
deadline `embed` keeps running. It is excluded from `rake test` because it is
|
|
56
|
+
timing-sensitive; run it with `rake cancellation_timing`.
|
|
57
|
+
|
|
58
|
+
- **`validate_encoding: :full | :prefix`** on `embed`, `embed_batch`,
|
|
59
|
+
`embed_with_stats` and `tokenize`. `embed` has to establish that its `String`
|
|
60
|
+
is valid UTF-8, and when Ruby has not computed the coderange yet that scan is
|
|
61
|
+
O(total bytes) and runs before the prefix window is chosen — so the
|
|
62
|
+
large-input path was not actually bounded on a freshly read document.
|
|
63
|
+
`:prefix` skips the up-front scan and lets the tokenizer validate the bytes it
|
|
64
|
+
reads: 442 µs to 78 µs on a 3 MB string, against 71 µs for the same string
|
|
65
|
+
with its coderange cached. `:full` stays the default, because `:prefix`
|
|
66
|
+
changes behaviour — invalid bytes past the truncation window are no longer
|
|
67
|
+
seen. A coderange Ruby has already computed is honoured in both modes.
|
|
68
|
+
|
|
69
|
+
- **Optional C allocation accounting.** Building with
|
|
70
|
+
`STATIC_EMBEDDINGS_ALLOC_STATS=1` wraps the runtime's own allocations and
|
|
71
|
+
exposes per-category bytes and counts, which the sample harness prints as
|
|
72
|
+
`c_alloc.<category>.<metric>`. Default builds do not define the internal
|
|
73
|
+
methods and do not pay for the counters. See `docs/PERFORMANCE.md`.
|
|
74
|
+
|
|
75
|
+
- **CI jobs `parity_fuzz`, `timing`, `alloc_stats` and `windows_loader`,** plus
|
|
76
|
+
a sanitizer build of the instrumented allocator inside the existing `memory`
|
|
77
|
+
job. The Windows job cross-compiles with mingw-w64 and runs the result under
|
|
78
|
+
wine, because that branch of `se_model_open` is the one path no other job
|
|
79
|
+
builds. `alloc_stats` is separate because it has to `rake clobber` first, and
|
|
80
|
+
clobber removes `tmp/`.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **The ASCII fast path checks the cancellation flag** on the same cadence as
|
|
85
|
+
the main loop. A run of ASCII bytes is consumed inside one C call, so without
|
|
86
|
+
the check a `max_tokens: false` call on a large document ignored its deadline
|
|
87
|
+
until the whole document was tokenized: 0.33 s against a 0.05 s `Timeout` on a
|
|
88
|
+
16 MB input, versus 0.05 s now.
|
|
89
|
+
|
|
90
|
+
- **`ext/static_embeddings/static_embeddings.c` was split.** The f16 codec and
|
|
91
|
+
kernels moved to `se_f16.c`, the top-k kernels to `se_topk.c`, and the
|
|
92
|
+
allocation counters live in `se_alloc_stats.c`. Shared overflow-checked size
|
|
93
|
+
helpers, `static_assert`s on the mmapped struct layouts, and an explicit
|
|
94
|
+
rejection of big-endian hosts moved into `se_internal.h`.
|
|
95
|
+
|
|
96
|
+
- **`Format.verify` streams the file.** It read the whole model and then
|
|
97
|
+
duplicated it to zero the checksum field, so verifying a 135 MB artifact
|
|
98
|
+
needed roughly 270 MB of transient `String` before hashing started. Peak is
|
|
99
|
+
now one 1 MiB chunk: measured on a 62 MB model, 433 ms and 280 MB of peak RSS
|
|
100
|
+
down to 249 ms and 30 MB.
|
|
101
|
+
|
|
102
|
+
- **Windows maps the model instead of reading it into the heap.** The POSIX
|
|
103
|
+
loader has always used `mmap`; Windows did `fopen` plus `fread` into a
|
|
104
|
+
private allocation, so every process paid a full read before the first query
|
|
105
|
+
and a private copy of the whole model. It now uses
|
|
106
|
+
`CreateFileMapping`/`MapViewOfFile`, with the old read path kept as a
|
|
107
|
+
fallback for filesystems that refuse to map. Verified by cross-compiling with
|
|
108
|
+
mingw-w64 and running under wine against a real `.semb`: same `map_size`,
|
|
109
|
+
same vectors as the POSIX build, and `mapped=1`.
|
|
110
|
+
|
|
111
|
+
- **Throughput figures separate logical from processed bytes.**
|
|
112
|
+
`tools/benchmark.rb` reports how many texts were truncated and refuses a
|
|
113
|
+
ns/byte figure when truncation makes the two differ; the large-input samples
|
|
114
|
+
print a measured `processed_input_mb_per_sec` alongside the logical one.
|
|
115
|
+
|
|
116
|
+
- **`samples/run_all.sh` captures `rake test`** into `00_test.log`, so a run
|
|
117
|
+
directory can back both "samples green" and "tests green". `TEST=0` skips it.
|
|
118
|
+
|
|
119
|
+
- **Documentation.** The README no longer implies `embed` is constant-time in
|
|
120
|
+
input size (the prefix window bounds the tokenizer, not the UTF-8 validity
|
|
121
|
+
scan) and no longer claims `f16` is faster, since this repository's own
|
|
122
|
+
samples show it winning on x86-64 F16C and losing on M1 Pro.
|
|
123
|
+
|
|
3
124
|
## 0.1.1 (unreleased)
|
|
4
125
|
|
|
5
126
|
Safety and correctness release. Everything here was found by re-reviewing 0.1.0
|