static_embeddings 0.1.4 → 0.1.5
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 +68 -0
- data/README.md +50 -23
- data/Rakefile +1 -1
- data/docs/ARCHITECTURE.md +42 -31
- data/docs/LIMITATIONS.md +12 -6
- data/docs/MODEL_AUDIT.md +85 -52
- data/ext/static_embeddings/se_embed.c +2 -1
- data/ext/static_embeddings/se_format.c +41 -4
- data/ext/static_embeddings/se_internal.h +17 -5
- data/ext/static_embeddings/se_tokenizer.c +155 -18
- data/ext/static_embeddings/se_unicode.c +1 -1
- data/ext/static_embeddings/static_embeddings.c +32 -2
- data/lib/static_embeddings/cli.rb +1 -0
- data/lib/static_embeddings/converter.rb +51 -7
- data/lib/static_embeddings/format.rb +60 -22
- data/lib/static_embeddings/paths.rb +18 -1
- data/lib/static_embeddings/reference.rb +54 -7
- data/lib/static_embeddings/safetensors.rb +178 -34
- data/lib/static_embeddings/version.rb +1 -1
- data/lib/static_embeddings.rb +3 -5
- data/tools/check_model2vec_parity.rb +85 -54
- metadata +1 -1
|
@@ -102,7 +102,7 @@ int se_vocab_lookup_piece(const se_model_t *model, const uint8_t *prefix, size_t
|
|
|
102
102
|
uint32_t pos = h & mask;
|
|
103
103
|
uint32_t total_len = (uint32_t)(prefix_len + len);
|
|
104
104
|
|
|
105
|
-
for (uint32_t probe = 0; probe
|
|
105
|
+
for (uint32_t probe = 0; probe < model->meta.max_probe; probe++) {
|
|
106
106
|
const se_vocab_slot_t *slot = &model->vocab_hash[pos];
|
|
107
107
|
if (slot->token_id == SE_SLOT_EMPTY)
|
|
108
108
|
return 0;
|
|
@@ -556,6 +556,31 @@ static se_status_t validate_vocab_hash(const se_model_t *model, struct section v
|
|
|
556
556
|
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
557
557
|
"[UNK] is not reachable through the vocabulary hash");
|
|
558
558
|
rc = SE_ERR_INVALID_FORMAT;
|
|
559
|
+
goto done;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
static const struct {
|
|
563
|
+
uint32_t bit;
|
|
564
|
+
const char *text;
|
|
565
|
+
uint32_t len;
|
|
566
|
+
} added[] = {
|
|
567
|
+
{SE_ADDED_PAD, "[PAD]", 5u}, {SE_ADDED_UNK, "[UNK]", 5u}, {SE_ADDED_CLS, "[CLS]", 5u},
|
|
568
|
+
{SE_ADDED_SEP, "[SEP]", 5u}, {SE_ADDED_MASK, "[MASK]", 6u},
|
|
569
|
+
};
|
|
570
|
+
const uint32_t ids[] = {m->pad_id, m->unk_id, m->cls_id, m->sep_id, m->mask_id};
|
|
571
|
+
|
|
572
|
+
for (size_t i = 0; i < SE_ARRAY_LEN(added); i++) {
|
|
573
|
+
if ((m->added_token_mask & added[i].bit) == 0)
|
|
574
|
+
continue;
|
|
575
|
+
uint32_t got = 0;
|
|
576
|
+
if (ids[i] >= m->vocab_size ||
|
|
577
|
+
!se_vocab_lookup(model, (const uint8_t *)added[i].text, added[i].len, &got) ||
|
|
578
|
+
got != ids[i]) {
|
|
579
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
580
|
+
"added token %s is not reachable at its recorded id", added[i].text);
|
|
581
|
+
rc = SE_ERR_INVALID_FORMAT;
|
|
582
|
+
goto done;
|
|
583
|
+
}
|
|
559
584
|
}
|
|
560
585
|
|
|
561
586
|
done:
|
|
@@ -646,6 +671,7 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
646
671
|
m->subword_prefix_len = read_u32(base, SE_OFF_SUBWORD_PREFIX_LEN);
|
|
647
672
|
m->max_token_chars = read_u32(base, SE_OFF_MAX_TOKEN_CHARS);
|
|
648
673
|
m->max_probe = read_u32(base, SE_OFF_MAX_PROBE);
|
|
674
|
+
m->added_token_mask = read_u32(base, SE_OFF_ADDED_TOKEN_MASK);
|
|
649
675
|
memcpy(m->subword_prefix, base + SE_OFF_SUBWORD_PREFIX, 8);
|
|
650
676
|
|
|
651
677
|
if (m->tokenizer_type != SE_TOKENIZER_BERT_WORDPIECE_V1) {
|
|
@@ -669,7 +695,7 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
669
695
|
m->normalization_type);
|
|
670
696
|
return SE_ERR_INVALID_FORMAT;
|
|
671
697
|
}
|
|
672
|
-
if (m->truncation_policy !=
|
|
698
|
+
if (m->truncation_policy != SE_TRUNCATE_USABLE_IDS_BEFORE_POOLING) {
|
|
673
699
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "truncation policy %u is not supported",
|
|
674
700
|
m->truncation_policy);
|
|
675
701
|
return SE_ERR_INVALID_FORMAT;
|
|
@@ -682,6 +708,11 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
682
708
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown UNK policy %u", m->unk_policy);
|
|
683
709
|
return SE_ERR_INVALID_FORMAT;
|
|
684
710
|
}
|
|
711
|
+
if ((m->added_token_mask & ~SE_ADDED_TOKEN_MASK_ALL) != 0) {
|
|
712
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown added-token mask bits 0x%x",
|
|
713
|
+
m->added_token_mask & ~SE_ADDED_TOKEN_MASK_ALL);
|
|
714
|
+
return SE_ERR_INVALID_FORMAT;
|
|
715
|
+
}
|
|
685
716
|
if (m->empty_policy != SE_EMPTY_ZERO_VECTOR && m->empty_policy != SE_EMPTY_RAISE) {
|
|
686
717
|
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown empty-input policy %u", m->empty_policy);
|
|
687
718
|
return SE_ERR_INVALID_FORMAT;
|
|
@@ -704,8 +735,14 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
|
704
735
|
m->hash_table_size);
|
|
705
736
|
return SE_ERR_INVALID_FORMAT;
|
|
706
737
|
}
|
|
707
|
-
if (m->hash_table_size
|
|
708
|
-
|
|
738
|
+
if (m->hash_table_size <= m->vocab_size ||
|
|
739
|
+
(uint64_t)m->vocab_size * 100u > (uint64_t)m->hash_table_size * 70u) {
|
|
740
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
741
|
+
"hash table load factor exceeds the supported 0.70 maximum");
|
|
742
|
+
return SE_ERR_INVALID_FORMAT;
|
|
743
|
+
}
|
|
744
|
+
if (m->max_probe == 0 || m->max_probe > m->hash_table_size) {
|
|
745
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "invalid recorded max probe %u", m->max_probe);
|
|
709
746
|
return SE_ERR_INVALID_FORMAT;
|
|
710
747
|
}
|
|
711
748
|
if (m->subword_prefix_len > 8) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#define SE_MAGIC "SEMBv1\0\0"
|
|
10
10
|
#define SE_MAGIC_LEN 8
|
|
11
|
-
#define SE_FORMAT_VERSION
|
|
11
|
+
#define SE_FORMAT_VERSION 3u
|
|
12
12
|
#define SE_HEADER_SIZE 320u
|
|
13
13
|
#define SE_ALIGNMENT 64u
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#define SE_NORMALIZATION_NONE 0u
|
|
19
19
|
#define SE_NORMALIZATION_L2 1u
|
|
20
20
|
|
|
21
|
-
#define
|
|
21
|
+
#define SE_TRUNCATE_USABLE_IDS_BEFORE_POOLING 2u
|
|
22
22
|
|
|
23
23
|
#define SE_UNK_INCLUDE 0u
|
|
24
24
|
#define SE_UNK_DROP 1u
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
#define SE_EMPTY_ZERO_VECTOR 0u
|
|
27
27
|
#define SE_EMPTY_RAISE 1u
|
|
28
28
|
|
|
29
|
+
#define SE_ADDED_PAD (1u << 0)
|
|
30
|
+
#define SE_ADDED_UNK (1u << 1)
|
|
31
|
+
#define SE_ADDED_CLS (1u << 2)
|
|
32
|
+
#define SE_ADDED_SEP (1u << 3)
|
|
33
|
+
#define SE_ADDED_MASK (1u << 4)
|
|
34
|
+
#define SE_ADDED_TOKEN_MASK_ALL \
|
|
35
|
+
(SE_ADDED_PAD | SE_ADDED_UNK | SE_ADDED_CLS | SE_ADDED_SEP | SE_ADDED_MASK)
|
|
36
|
+
|
|
29
37
|
#define SE_SLOT_EMPTY 0xFFFFFFFFu
|
|
30
38
|
#define SE_SIZE_MAX ((size_t)-1)
|
|
31
39
|
|
|
@@ -83,7 +91,8 @@ enum {
|
|
|
83
91
|
SE_OFF_SEC_ROOT_TRIE = 208,
|
|
84
92
|
SE_OFF_SEC_CONT_TRIE = 224,
|
|
85
93
|
SE_OFF_CHECKSUM = 240,
|
|
86
|
-
SE_OFF_MAX_PROBE = 304
|
|
94
|
+
SE_OFF_MAX_PROBE = 304,
|
|
95
|
+
SE_OFF_ADDED_TOKEN_MASK = 308
|
|
87
96
|
};
|
|
88
97
|
|
|
89
98
|
typedef struct {
|
|
@@ -163,6 +172,7 @@ typedef struct {
|
|
|
163
172
|
uint32_t max_input_chars_per_word;
|
|
164
173
|
uint32_t max_token_chars;
|
|
165
174
|
uint32_t max_probe;
|
|
175
|
+
uint32_t added_token_mask;
|
|
166
176
|
uint32_t pad_id;
|
|
167
177
|
uint32_t unk_id;
|
|
168
178
|
uint32_t cls_id;
|
|
@@ -401,9 +411,11 @@ typedef struct {
|
|
|
401
411
|
uint32_t truncated;
|
|
402
412
|
} se_token_stats_t;
|
|
403
413
|
|
|
414
|
+
typedef enum { SE_TOKEN_LIMIT_RAW = 0, SE_TOKEN_LIMIT_USABLE = 1 } se_token_limit_t;
|
|
415
|
+
|
|
404
416
|
se_status_t se_tokenize(const se_model_t *model, se_scratch_t *scratch, const uint8_t *input,
|
|
405
|
-
size_t input_len, uint32_t max_tokens,
|
|
406
|
-
se_error_t *err, volatile sig_atomic_t *cancelled);
|
|
417
|
+
size_t input_len, uint32_t max_tokens, se_token_limit_t limit_mode,
|
|
418
|
+
se_token_stats_t *stats, se_error_t *err, volatile sig_atomic_t *cancelled);
|
|
407
419
|
|
|
408
420
|
se_status_t se_embed_one(const se_model_t *model, se_scratch_t *scratch, const uint8_t *input,
|
|
409
421
|
size_t input_len, uint32_t max_tokens, float *out, se_token_stats_t *stats,
|
|
@@ -87,16 +87,17 @@ static int push_id(se_scratch_t *sc, size_t *n_ids, uint32_t id) {
|
|
|
87
87
|
return 1;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
#define SE_SCRATCH_CPS_KEEP
|
|
91
|
-
#define
|
|
92
|
-
#define
|
|
90
|
+
#define SE_SCRATCH_CPS_KEEP 256
|
|
91
|
+
#define SE_SCRATCH_IDS_RESERVE 512
|
|
92
|
+
#define SE_SCRATCH_IDS_RETAIN_MAX 8192
|
|
93
|
+
#define SE_SCRATCH_BYTES_KEEP 1024
|
|
93
94
|
|
|
94
95
|
int se_scratch_reserve(se_scratch_t *s, uint32_t dim) {
|
|
95
96
|
if (!grow_u32(&s->cps, &s->cps_cap, SE_SCRATCH_CPS_KEEP))
|
|
96
97
|
return 0;
|
|
97
98
|
if (!grow_u32(&s->cps2, &s->cps2_cap, SE_SCRATCH_CPS_KEEP))
|
|
98
99
|
return 0;
|
|
99
|
-
if (!grow_u32(&s->ids, &s->ids_cap,
|
|
100
|
+
if (!grow_u32(&s->ids, &s->ids_cap, SE_SCRATCH_IDS_RESERVE))
|
|
100
101
|
return 0;
|
|
101
102
|
if (!grow_bytes(&s->bytes, &s->bytes_cap, SE_SCRATCH_BYTES_KEEP))
|
|
102
103
|
return 0;
|
|
@@ -149,7 +150,8 @@ static int shrink_bytes(uint8_t **buf, size_t *cap, size_t keep) {
|
|
|
149
150
|
static void se_scratch_trim(se_scratch_t *s) {
|
|
150
151
|
(void)shrink_u32(&s->cps, &s->cps_cap, SE_SCRATCH_CPS_KEEP);
|
|
151
152
|
(void)shrink_u32(&s->cps2, &s->cps2_cap, SE_SCRATCH_CPS_KEEP);
|
|
152
|
-
(
|
|
153
|
+
if (s->ids_cap > SE_SCRATCH_IDS_RETAIN_MAX)
|
|
154
|
+
(void)shrink_u32(&s->ids, &s->ids_cap, SE_SCRATCH_IDS_RETAIN_MAX);
|
|
153
155
|
(void)shrink_bytes(&s->bytes, &s->bytes_cap, SE_SCRATCH_BYTES_KEEP);
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -393,6 +395,37 @@ static int cp_is_cjk_segment(const se_model_t *m, uint32_t cp) {
|
|
|
393
395
|
return m->meta.handle_chinese_chars && cp >= 0x3400 && se_is_cjk(cp);
|
|
394
396
|
}
|
|
395
397
|
|
|
398
|
+
typedef struct {
|
|
399
|
+
const char *text;
|
|
400
|
+
uint32_t len;
|
|
401
|
+
uint32_t bit;
|
|
402
|
+
} se_added_token_spec_t;
|
|
403
|
+
|
|
404
|
+
static const se_added_token_spec_t SE_ADDED_TOKENS[] = {
|
|
405
|
+
{"[MASK]", 6u, SE_ADDED_MASK}, {"[PAD]", 5u, SE_ADDED_PAD}, {"[UNK]", 5u, SE_ADDED_UNK},
|
|
406
|
+
{"[CLS]", 5u, SE_ADDED_CLS}, {"[SEP]", 5u, SE_ADDED_SEP},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
static int boundary_splits_added_token(const se_model_t *model, const uint8_t *input,
|
|
410
|
+
size_t input_len, size_t boundary) {
|
|
411
|
+
if (model->meta.added_token_mask == 0 || boundary == 0 || boundary >= input_len)
|
|
412
|
+
return 0;
|
|
413
|
+
|
|
414
|
+
for (size_t k = 0; k < SE_ARRAY_LEN(SE_ADDED_TOKENS); k++) {
|
|
415
|
+
const se_added_token_spec_t *token = &SE_ADDED_TOKENS[k];
|
|
416
|
+
if ((model->meta.added_token_mask & token->bit) == 0)
|
|
417
|
+
continue;
|
|
418
|
+
for (size_t back = 1; back < token->len && back <= boundary; back++) {
|
|
419
|
+
size_t start = boundary - back;
|
|
420
|
+
if (start + token->len > input_len || input[start] != '[')
|
|
421
|
+
continue;
|
|
422
|
+
if (memcmp(input + start, token->text, token->len) == 0)
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
396
429
|
size_t se_prefix_boundary_len(const se_model_t *model, const uint8_t *input, size_t input_len,
|
|
397
430
|
size_t target, size_t backscan) {
|
|
398
431
|
if (target >= input_len)
|
|
@@ -420,14 +453,15 @@ size_t se_prefix_boundary_len(const se_model_t *model, const uint8_t *input, siz
|
|
|
420
453
|
continue;
|
|
421
454
|
|
|
422
455
|
if (cp_is_cjk_segment(model, cp)) {
|
|
423
|
-
if (after <= target)
|
|
456
|
+
if (after <= target && !boundary_splits_added_token(model, input, input_len, after))
|
|
424
457
|
return after;
|
|
425
|
-
if (pos > 0)
|
|
458
|
+
if (pos > 0 && !boundary_splits_added_token(model, input, input_len, pos))
|
|
426
459
|
return pos;
|
|
427
|
-
|
|
460
|
+
continue;
|
|
428
461
|
}
|
|
429
462
|
|
|
430
|
-
if ((is_whitespace(model, cp) || is_punct(model, cp)) && after <= target
|
|
463
|
+
if ((is_whitespace(model, cp) || is_punct(model, cp)) && after <= target &&
|
|
464
|
+
!boundary_splits_added_token(model, input, input_len, after))
|
|
431
465
|
return after;
|
|
432
466
|
}
|
|
433
467
|
|
|
@@ -454,6 +488,7 @@ typedef struct {
|
|
|
454
488
|
const se_model_t *model;
|
|
455
489
|
se_scratch_t *scratch;
|
|
456
490
|
uint32_t max_tokens;
|
|
491
|
+
se_token_limit_t limit_mode;
|
|
457
492
|
size_t n_ids;
|
|
458
493
|
size_t n_unk;
|
|
459
494
|
size_t segment_len;
|
|
@@ -481,17 +516,44 @@ static int append_segment_cp(token_state_t *st, uint32_t cp) {
|
|
|
481
516
|
return 1;
|
|
482
517
|
}
|
|
483
518
|
|
|
519
|
+
static size_t limited_token_count(const token_state_t *st) {
|
|
520
|
+
if (st->limit_mode == SE_TOKEN_LIMIT_USABLE && st->model->meta.unk_policy == SE_UNK_DROP)
|
|
521
|
+
return st->n_ids - st->n_unk;
|
|
522
|
+
return st->n_ids;
|
|
523
|
+
}
|
|
524
|
+
|
|
484
525
|
static int cap_after_append(token_state_t *st) {
|
|
485
|
-
if (st->max_tokens == 0 || st
|
|
526
|
+
if (st->max_tokens == 0 || limited_token_count(st) <= (size_t)st->max_tokens)
|
|
486
527
|
return 0;
|
|
487
528
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
529
|
+
if (st->limit_mode == SE_TOKEN_LIMIT_RAW || st->model->meta.unk_policy != SE_UNK_DROP) {
|
|
530
|
+
size_t dropped_unk = 0;
|
|
531
|
+
for (size_t k = st->max_tokens; k < st->n_ids; k++) {
|
|
532
|
+
if (st->scratch->ids[k] == st->model->meta.unk_id)
|
|
533
|
+
dropped_unk++;
|
|
534
|
+
}
|
|
535
|
+
st->n_unk -= dropped_unk;
|
|
536
|
+
st->n_ids = st->max_tokens;
|
|
537
|
+
} else {
|
|
538
|
+
size_t usable = 0;
|
|
539
|
+
size_t kept_unk = 0;
|
|
540
|
+
size_t keep = 0;
|
|
541
|
+
for (size_t k = 0; k < st->n_ids; k++) {
|
|
542
|
+
uint32_t id = st->scratch->ids[k];
|
|
543
|
+
if (id == st->model->meta.unk_id) {
|
|
544
|
+
kept_unk++;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
usable++;
|
|
548
|
+
if (usable == (size_t)st->max_tokens) {
|
|
549
|
+
keep = k + 1;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
st->n_ids = keep;
|
|
554
|
+
st->n_unk = kept_unk;
|
|
492
555
|
}
|
|
493
|
-
|
|
494
|
-
st->n_ids = st->max_tokens;
|
|
556
|
+
|
|
495
557
|
st->stats->truncated = 1;
|
|
496
558
|
return 1;
|
|
497
559
|
}
|
|
@@ -602,6 +664,54 @@ static wordpiece_status_t wordpiece(const se_model_t *m, se_scratch_t *sc, const
|
|
|
602
664
|
return 1;
|
|
603
665
|
}
|
|
604
666
|
|
|
667
|
+
static uint32_t added_token_id(const se_model_t *model, uint32_t bit) {
|
|
668
|
+
switch (bit) {
|
|
669
|
+
case SE_ADDED_PAD:
|
|
670
|
+
return model->meta.pad_id;
|
|
671
|
+
case SE_ADDED_UNK:
|
|
672
|
+
return model->meta.unk_id;
|
|
673
|
+
case SE_ADDED_CLS:
|
|
674
|
+
return model->meta.cls_id;
|
|
675
|
+
case SE_ADDED_SEP:
|
|
676
|
+
return model->meta.sep_id;
|
|
677
|
+
case SE_ADDED_MASK:
|
|
678
|
+
return model->meta.mask_id;
|
|
679
|
+
default:
|
|
680
|
+
return SE_SLOT_EMPTY;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
static int match_added_token(const se_model_t *model, const uint8_t *input, size_t input_len,
|
|
685
|
+
size_t pos, uint32_t *id_out, size_t *len_out) {
|
|
686
|
+
if (model->meta.added_token_mask == 0 || pos >= input_len || input[pos] != '[')
|
|
687
|
+
return 0;
|
|
688
|
+
|
|
689
|
+
for (size_t k = 0; k < SE_ARRAY_LEN(SE_ADDED_TOKENS); k++) {
|
|
690
|
+
const se_added_token_spec_t *token = &SE_ADDED_TOKENS[k];
|
|
691
|
+
if ((model->meta.added_token_mask & token->bit) == 0)
|
|
692
|
+
continue;
|
|
693
|
+
if (token->len > input_len - pos)
|
|
694
|
+
continue;
|
|
695
|
+
if (memcmp(input + pos, token->text, token->len) != 0)
|
|
696
|
+
continue;
|
|
697
|
+
|
|
698
|
+
*id_out = added_token_id(model, token->bit);
|
|
699
|
+
*len_out = token->len;
|
|
700
|
+
return 1;
|
|
701
|
+
}
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
static se_status_t append_direct_id(token_state_t *st, uint32_t id, int *stop) {
|
|
706
|
+
if (!push_id(st->scratch, &st->n_ids, id))
|
|
707
|
+
return oom(st, "adding a special token");
|
|
708
|
+
if (id == st->model->meta.unk_id)
|
|
709
|
+
st->n_unk++;
|
|
710
|
+
if (cap_after_append(st))
|
|
711
|
+
*stop = 1;
|
|
712
|
+
return SE_OK;
|
|
713
|
+
}
|
|
714
|
+
|
|
605
715
|
static se_status_t append_wordpiece(token_state_t *st, const uint32_t *word, size_t word_len,
|
|
606
716
|
int *stop) {
|
|
607
717
|
wordpiece_status_t wp =
|
|
@@ -752,6 +862,31 @@ static se_status_t tokenize_ascii_run(token_state_t *st, const uint8_t *input, s
|
|
|
752
862
|
if (b >= 0x80)
|
|
753
863
|
break;
|
|
754
864
|
|
|
865
|
+
if (b == '[' && st->model->meta.added_token_mask != 0) {
|
|
866
|
+
uint32_t added_id = 0;
|
|
867
|
+
size_t added_len = 0;
|
|
868
|
+
if (match_added_token(st->model, input, input_len, i, &added_id, &added_len)) {
|
|
869
|
+
se_status_t rc = flush_segment(st, stop);
|
|
870
|
+
if (rc != SE_OK) {
|
|
871
|
+
*ip = i;
|
|
872
|
+
return rc;
|
|
873
|
+
}
|
|
874
|
+
if (*stop) {
|
|
875
|
+
*ip = i;
|
|
876
|
+
return SE_OK;
|
|
877
|
+
}
|
|
878
|
+
rc = append_direct_id(st, added_id, stop);
|
|
879
|
+
if (rc != SE_OK) {
|
|
880
|
+
*ip = i;
|
|
881
|
+
return rc;
|
|
882
|
+
}
|
|
883
|
+
i += added_len;
|
|
884
|
+
if (*stop)
|
|
885
|
+
break;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
755
890
|
if (i >= next_cancel_check) {
|
|
756
891
|
if (token_cancelled(st)) {
|
|
757
892
|
*ip = i;
|
|
@@ -811,8 +946,9 @@ static se_status_t tokenize_ascii_run(token_state_t *st, const uint8_t *input, s
|
|
|
811
946
|
}
|
|
812
947
|
|
|
813
948
|
se_status_t se_tokenize(const se_model_t *model, se_scratch_t *sc, const uint8_t *input,
|
|
814
|
-
size_t input_len, uint32_t max_tokens,
|
|
815
|
-
|
|
949
|
+
size_t input_len, uint32_t max_tokens, se_token_limit_t limit_mode,
|
|
950
|
+
se_token_stats_t *stats, se_error_t *err,
|
|
951
|
+
volatile sig_atomic_t *cancelled) {
|
|
816
952
|
memset(stats, 0, sizeof(*stats));
|
|
817
953
|
|
|
818
954
|
token_state_t st;
|
|
@@ -820,6 +956,7 @@ se_status_t se_tokenize(const se_model_t *model, se_scratch_t *sc, const uint8_t
|
|
|
820
956
|
st.model = model;
|
|
821
957
|
st.scratch = sc;
|
|
822
958
|
st.max_tokens = max_tokens;
|
|
959
|
+
st.limit_mode = limit_mode;
|
|
823
960
|
st.stats = stats;
|
|
824
961
|
st.err = err;
|
|
825
962
|
st.cancelled = cancelled;
|
|
@@ -115,6 +115,6 @@ const se_map_entry_t *se_map_lookup(const se_map_entry_t *entries, uint32_t coun
|
|
|
115
115
|
int se_is_cjk(uint32_t cp) {
|
|
116
116
|
return (cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
117
117
|
(cp >= 0x20000 && cp <= 0x2A6DF) || (cp >= 0x2A700 && cp <= 0x2B73F) ||
|
|
118
|
-
(cp >= 0x2B740 && cp <= 0x2B81F) || (cp >=
|
|
118
|
+
(cp >= 0x2B740 && cp <= 0x2B81F) || (cp >= 0x2B920 && cp <= 0x2CEAF) ||
|
|
119
119
|
(cp >= 0xF900 && cp <= 0xFAFF) || (cp >= 0x2F800 && cp <= 0x2FA1F);
|
|
120
120
|
}
|
|
@@ -80,6 +80,7 @@ static ID id_blocking_p;
|
|
|
80
80
|
static ID id_vector;
|
|
81
81
|
static ID id_token_count;
|
|
82
82
|
static ID id_unk_count;
|
|
83
|
+
static ID id_pooled_count;
|
|
83
84
|
static ID id_truncated;
|
|
84
85
|
static ID id_dim;
|
|
85
86
|
static ID id_allow_unfrozen;
|
|
@@ -1030,6 +1031,10 @@ static VALUE model_embed_with_stats(int argc, VALUE *argv, VALUE self) {
|
|
|
1030
1031
|
rb_hash_aset(hash, ID2SYM(id_vector), vector);
|
|
1031
1032
|
rb_hash_aset(hash, ID2SYM(id_token_count), UINT2NUM(stats.token_count));
|
|
1032
1033
|
rb_hash_aset(hash, ID2SYM(id_unk_count), UINT2NUM(stats.unk_count));
|
|
1034
|
+
uint32_t pooled_count = get_model(self)->model.meta.unk_policy == SE_UNK_DROP
|
|
1035
|
+
? stats.token_count - stats.unk_count
|
|
1036
|
+
: stats.token_count;
|
|
1037
|
+
rb_hash_aset(hash, ID2SYM(id_pooled_count), UINT2NUM(pooled_count));
|
|
1033
1038
|
rb_hash_aset(hash, ID2SYM(id_truncated), stats.truncated ? Qtrue : Qfalse);
|
|
1034
1039
|
return hash;
|
|
1035
1040
|
}
|
|
@@ -1049,7 +1054,7 @@ typedef struct {
|
|
|
1049
1054
|
static VALUE tokenize_scratch_body(VALUE arg) {
|
|
1050
1055
|
tokenize_scratch_job_t *job = (tokenize_scratch_job_t *)(uintptr_t)arg;
|
|
1051
1056
|
job->rc = se_tokenize(job->model, job->scratch, job->input, job->input_len, job->max_tokens,
|
|
1052
|
-
&job->stats, &job->err, NULL);
|
|
1057
|
+
SE_TOKEN_LIMIT_RAW, &job->stats, &job->err, NULL);
|
|
1053
1058
|
if (job->rc != SE_OK)
|
|
1054
1059
|
return Qnil;
|
|
1055
1060
|
|
|
@@ -1237,7 +1242,27 @@ static VALUE embed_token_ids_value(VALUE self, VALUE ids_value, VALUE max_tokens
|
|
|
1237
1242
|
size_t n = (size_t)n_long;
|
|
1238
1243
|
int truncated = 0;
|
|
1239
1244
|
|
|
1240
|
-
if (max_tokens != 0 &&
|
|
1245
|
+
if (max_tokens != 0 && w->model.meta.unk_policy == SE_UNK_DROP) {
|
|
1246
|
+
size_t usable = 0;
|
|
1247
|
+
size_t cutoff = n;
|
|
1248
|
+
for (size_t i = 0; i < n; i++) {
|
|
1249
|
+
unsigned long long value = NUM2ULL(rb_ary_entry(ids_value, (long)i));
|
|
1250
|
+
if (value >= w->model.meta.vocab_size)
|
|
1251
|
+
rb_raise(rb_eArgError, "token id at index %zu is out of range", i);
|
|
1252
|
+
|
|
1253
|
+
if ((uint32_t)value == w->model.meta.unk_id)
|
|
1254
|
+
continue;
|
|
1255
|
+
|
|
1256
|
+
usable++;
|
|
1257
|
+
if (usable == (size_t)max_tokens) {
|
|
1258
|
+
cutoff = i + 1;
|
|
1259
|
+
} else if (usable > (size_t)max_tokens) {
|
|
1260
|
+
n = cutoff;
|
|
1261
|
+
truncated = 1;
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
} else if (max_tokens != 0 && n > (size_t)max_tokens) {
|
|
1241
1266
|
n = (size_t)max_tokens;
|
|
1242
1267
|
truncated = 1;
|
|
1243
1268
|
}
|
|
@@ -1297,6 +1322,10 @@ static VALUE model_embed_token_ids_with_stats(int argc, VALUE *argv, VALUE self)
|
|
|
1297
1322
|
rb_hash_aset(hash, ID2SYM(id_vector), vector);
|
|
1298
1323
|
rb_hash_aset(hash, ID2SYM(id_token_count), UINT2NUM(stats.token_count));
|
|
1299
1324
|
rb_hash_aset(hash, ID2SYM(id_unk_count), UINT2NUM(stats.unk_count));
|
|
1325
|
+
uint32_t pooled_count = get_model(self)->model.meta.unk_policy == SE_UNK_DROP
|
|
1326
|
+
? stats.token_count - stats.unk_count
|
|
1327
|
+
: stats.token_count;
|
|
1328
|
+
rb_hash_aset(hash, ID2SYM(id_pooled_count), UINT2NUM(pooled_count));
|
|
1300
1329
|
rb_hash_aset(hash, ID2SYM(id_truncated), stats.truncated ? Qtrue : Qfalse);
|
|
1301
1330
|
return hash;
|
|
1302
1331
|
}
|
|
@@ -1607,6 +1636,7 @@ RUBY_FUNC_EXPORTED void Init_static_embeddings(void) {
|
|
|
1607
1636
|
id_vector = rb_intern("vector");
|
|
1608
1637
|
id_token_count = rb_intern("token_count");
|
|
1609
1638
|
id_unk_count = rb_intern("unk_count");
|
|
1639
|
+
id_pooled_count = rb_intern("pooled_count");
|
|
1610
1640
|
id_truncated = rb_intern("truncated");
|
|
1611
1641
|
id_dim = rb_intern("dim");
|
|
1612
1642
|
id_allow_unfrozen = rb_intern("allow_unfrozen");
|
|
@@ -65,6 +65,7 @@ module StaticEmbeddings
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def convert(argv)
|
|
68
|
+
require "static_embeddings/converter"
|
|
68
69
|
options = parse_convert_options(argv)
|
|
69
70
|
source = required_arg(argv, "usage: static_embeddings convert SOURCE_DIR [--out PATH]")
|
|
70
71
|
model_id = options[:id] || File.basename(File.expand_path(source))
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "digest"
|
|
3
|
+
require "static_embeddings/format"
|
|
4
|
+
require "static_embeddings/safetensors"
|
|
5
|
+
require "static_embeddings/unicode_tables"
|
|
3
6
|
|
|
4
7
|
module StaticEmbeddings
|
|
5
8
|
class Converter
|
|
6
9
|
REFERENCE_IMPL = "model2vec.StaticModel"
|
|
10
|
+
REFERENCE_MODEL2VEC_VERSION = "0.9.0"
|
|
11
|
+
REFERENCE_TOKENIZERS_VERSION = "0.23.1"
|
|
12
|
+
REFERENCE_UNICODE_CATEGORIES_VERSION = "0.1.1"
|
|
7
13
|
REFERENCE_MAX_TOKENS = 512
|
|
8
14
|
|
|
9
15
|
ALLOWED_NORMALIZER_KEYS = %w[type clean_text handle_chinese_chars strip_accents lowercase].freeze
|
|
10
|
-
STANDARD_SPECIAL_TOKENS =
|
|
16
|
+
STANDARD_SPECIAL_TOKENS = {
|
|
17
|
+
"[PAD]" => Format::ADDED_PAD,
|
|
18
|
+
"[UNK]" => Format::ADDED_UNK,
|
|
19
|
+
"[CLS]" => Format::ADDED_CLS,
|
|
20
|
+
"[SEP]" => Format::ADDED_SEP,
|
|
21
|
+
"[MASK]" => Format::ADDED_MASK
|
|
22
|
+
}.freeze
|
|
11
23
|
SOURCE_FILES = %w[tokenizer.json config.json tokenizer_config.json model.safetensors].freeze
|
|
12
24
|
TOKENIZER_PROFILE = "BERT_WORDPIECE_V1"
|
|
13
25
|
|
|
@@ -22,6 +34,7 @@ module StaticEmbeddings
|
|
|
22
34
|
source = load_source
|
|
23
35
|
profile = audit_tokenizer(source[:tokenizer], source[:tokenizer_config])
|
|
24
36
|
tokens = extract_vocab(source[:tokenizer])
|
|
37
|
+
validate_added_token_ids!(profile[:added_tokens], tokens)
|
|
25
38
|
matrix, dim = extract_matrix(tokens.length)
|
|
26
39
|
meta = runtime_meta(source[:config], profile, tokens, max_tokens)
|
|
27
40
|
|
|
@@ -62,9 +75,13 @@ module StaticEmbeddings
|
|
|
62
75
|
assert_wordpiece!(model)
|
|
63
76
|
assert_normalizer!(normalizer)
|
|
64
77
|
assert_pre_tokenizer!(pre_tokenizer)
|
|
65
|
-
audit_added_tokens(tokenizer)
|
|
78
|
+
added_tokens = audit_added_tokens(tokenizer)
|
|
66
79
|
|
|
67
80
|
profile = profile_from(model, normalizer, tokenizer_config)
|
|
81
|
+
profile[:added_tokens] = added_tokens
|
|
82
|
+
profile[:added_token_mask] = added_tokens.reduce(0) do |mask, token|
|
|
83
|
+
mask | STANDARD_SPECIAL_TOKENS.fetch(token.fetch("content"))
|
|
84
|
+
end
|
|
68
85
|
reject!("clean_text=false is not supported by the runtime") unless profile[:clean_text]
|
|
69
86
|
profile
|
|
70
87
|
end
|
|
@@ -123,17 +140,40 @@ module StaticEmbeddings
|
|
|
123
140
|
def audit_added_tokens(tokenizer)
|
|
124
141
|
added = tokenizer["added_tokens"] || []
|
|
125
142
|
bad_content = added.reject { |token| standard_special?(token) }
|
|
126
|
-
|
|
143
|
+
unless bad_content.empty?
|
|
144
|
+
reject!("tokenizer declares non-standard added_tokens #{bad_content.map { |t| t['content'] }.inspect}")
|
|
145
|
+
end
|
|
127
146
|
|
|
128
147
|
whitespace = added.find { |token| token["content"].to_s.match?(/\s/) }
|
|
129
148
|
reject!("added token #{whitespace['content'].inspect} contains whitespace") if whitespace
|
|
130
149
|
|
|
131
150
|
flagged = added.find { |token| token["lstrip"] || token["rstrip"] || token["single_word"] }
|
|
132
151
|
reject!("added token #{flagged['content'].inspect} uses lstrip/rstrip/single_word") if flagged
|
|
152
|
+
|
|
153
|
+
normalized = added.find { |token| token["normalized"] != false }
|
|
154
|
+
if normalized
|
|
155
|
+
reject!("added token #{normalized['content'].inspect} must use normalized=false")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
duplicate = added.group_by { |token| token["content"] }.find { |_, rows| rows.length > 1 }
|
|
159
|
+
reject!("duplicate added token #{duplicate[0].inspect}") if duplicate
|
|
160
|
+
|
|
161
|
+
added
|
|
133
162
|
end
|
|
134
163
|
|
|
135
164
|
def standard_special?(token)
|
|
136
|
-
token["special"] && STANDARD_SPECIAL_TOKENS.
|
|
165
|
+
token["special"] && STANDARD_SPECIAL_TOKENS.key?(token["content"])
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def validate_added_token_ids!(added_tokens, tokens)
|
|
169
|
+
added_tokens.each do |token|
|
|
170
|
+
content = token.fetch("content")
|
|
171
|
+
id = token["id"]
|
|
172
|
+
reject!("added token #{content.inspect} has non-integer id #{id.inspect}") unless id.is_a?(Integer)
|
|
173
|
+
unless id.between?(0, tokens.length - 1) && tokens[id] == content
|
|
174
|
+
reject!("added token #{content.inspect} id #{id} does not match model.vocab")
|
|
175
|
+
end
|
|
176
|
+
end
|
|
137
177
|
end
|
|
138
178
|
|
|
139
179
|
def extract_vocab(tokenizer)
|
|
@@ -159,7 +199,7 @@ module StaticEmbeddings
|
|
|
159
199
|
path = File.join(source_dir, "model.safetensors")
|
|
160
200
|
raise ConversionError, "missing model.safetensors in #{source_dir}" unless File.file?(path)
|
|
161
201
|
|
|
162
|
-
name, tensor = sole_matrix_tensor(Safetensors.
|
|
202
|
+
name, tensor = sole_matrix_tensor(Safetensors.describe(path)[:tensors])
|
|
163
203
|
rows, dim = tensor[:shape]
|
|
164
204
|
if rows != vocab_size
|
|
165
205
|
raise ConversionError,
|
|
@@ -167,7 +207,7 @@ module StaticEmbeddings
|
|
|
167
207
|
"#{vocab_size} tokens — refusing to guess the mapping"
|
|
168
208
|
end
|
|
169
209
|
|
|
170
|
-
[tensor
|
|
210
|
+
[Safetensors.f32_payload(path, tensor), dim]
|
|
171
211
|
end
|
|
172
212
|
|
|
173
213
|
def sole_matrix_tensor(tensors)
|
|
@@ -203,6 +243,7 @@ module StaticEmbeddings
|
|
|
203
243
|
strip_accents: profile[:strip_accents],
|
|
204
244
|
handle_chinese_chars: profile[:handle_chinese_chars],
|
|
205
245
|
clean_text: profile[:clean_text],
|
|
246
|
+
added_token_mask: profile.fetch(:added_token_mask),
|
|
206
247
|
max_input_chars_per_word: profile[:max_input_chars_per_word],
|
|
207
248
|
max_token_chars: max_token_chars(tokens, profile[:continuing_subword_prefix]),
|
|
208
249
|
subword_prefix: profile[:continuing_subword_prefix]
|
|
@@ -241,7 +282,7 @@ module StaticEmbeddings
|
|
|
241
282
|
def source_digests
|
|
242
283
|
SOURCE_FILES.each_with_object({}) do |name, acc|
|
|
243
284
|
path = File.join(source_dir, name)
|
|
244
|
-
acc[name] = Digest::SHA256.
|
|
285
|
+
acc[name] = Digest::SHA256.file(path).hexdigest if File.file?(path)
|
|
245
286
|
end
|
|
246
287
|
end
|
|
247
288
|
|
|
@@ -252,6 +293,9 @@ module StaticEmbeddings
|
|
|
252
293
|
"source_model_id" => model_id || File.basename(File.expand_path(source_dir)),
|
|
253
294
|
"source_files_sha256" => source_digests,
|
|
254
295
|
"reference_impl" => REFERENCE_IMPL,
|
|
296
|
+
"reference_model2vec_version" => REFERENCE_MODEL2VEC_VERSION,
|
|
297
|
+
"reference_tokenizers_version" => REFERENCE_TOKENIZERS_VERSION,
|
|
298
|
+
"reference_unicode_categories_version" => REFERENCE_UNICODE_CATEGORIES_VERSION,
|
|
255
299
|
"reference_max_tokens" => meta[:max_tokens_default],
|
|
256
300
|
"unicode_source" => UnicodeTables.source_stamp,
|
|
257
301
|
"tokenizer_profile" => TOKENIZER_PROFILE,
|