static_embeddings 0.1.1 → 0.1.3

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.
@@ -15,25 +15,6 @@
15
15
  #include <stdlib.h>
16
16
  #include <string.h>
17
17
 
18
- #if defined(__ARM_NEON) || defined(__ARM_NEON__)
19
- #include <arm_neon.h>
20
- #define SE_HAVE_NEON 1
21
- #if defined(__aarch64__)
22
- #define SE_HAVE_NEON_FP16 1
23
- #endif
24
- #elif defined(__SSE__)
25
- #include <xmmintrin.h>
26
- #define SE_HAVE_SSE 1
27
- #endif
28
-
29
- #if defined(__x86_64__) || defined(__i386__)
30
- #if defined(__GNUC__) || defined(__clang__)
31
- #include <cpuid.h>
32
- #include <immintrin.h>
33
- #define SE_HAVE_X86_F16C_TARGET 1
34
- #endif
35
- #endif
36
-
37
18
  #include "se_internal.h"
38
19
 
39
20
  #if defined(__GNUC__) || defined(__clang__)
@@ -50,29 +31,6 @@
50
31
  #define SE_PREFIX_MIN_BYTES 4096
51
32
  #define SE_PREFIX_MAX_BYTES 65536
52
33
  #define SE_PREFIX_BACKSCAN_BYTES 8192
53
- #define SE_SIZE_MAX ((size_t)-1)
54
-
55
- typedef enum { SE_VECTOR_FORMAT_F32 = 1, SE_VECTOR_FORMAT_F16 = 2 } se_vector_format_t;
56
-
57
- typedef enum {
58
- SE_F16_BACKEND_LUT = 0,
59
- SE_F16_BACKEND_NEON_FP16 = 1,
60
- SE_F16_BACKEND_F16C = 2
61
- } se_f16_backend_t;
62
-
63
- static se_f16_backend_t se_f16_backend = SE_F16_BACKEND_LUT;
64
-
65
- #if !defined(SE_HAVE_NEON_FP16)
66
- #define SE_NEED_F16_LUT 1
67
- /* 256 KB of BSS, and only for builds that can fall back to it. It is populated
68
- * lazily by select_f16_backend, so an x86 machine with F16C never touches these
69
- * pages either. AArch64 does not compile the table at all. */
70
- static float se_f16_lut[65536];
71
- #endif
72
-
73
- static size_t vector_format_element_bytes(se_vector_format_t format) {
74
- return format == SE_VECTOR_FORMAT_F16 ? 2u : sizeof(float);
75
- }
76
34
 
77
35
  static int string_equals_literal(VALUE str, const char *lit) {
78
36
  size_t n = strlen(lit);
@@ -101,147 +59,6 @@ static se_vector_format_t resolve_vector_format(VALUE opt) {
101
59
  opt);
102
60
  }
103
61
 
104
- static void write_u16le(uint8_t *dst, uint16_t v) {
105
- dst[0] = (uint8_t)(v & 0xffu);
106
- dst[1] = (uint8_t)(v >> 8);
107
- }
108
-
109
- static uint16_t read_u16le(const uint8_t *src) {
110
- return (uint16_t)src[0] | ((uint16_t)src[1] << 8);
111
- }
112
-
113
- static uint16_t float_to_f16_bits(float value) {
114
- uint32_t bits;
115
- memcpy(&bits, &value, sizeof(bits));
116
-
117
- uint32_t sign = (bits >> 16) & 0x8000u;
118
- uint32_t exp = (bits >> 23) & 0xffu;
119
- uint32_t mant = bits & 0x7fffffu;
120
-
121
- if (exp == 0xffu) {
122
- if (mant == 0)
123
- return (uint16_t)(sign | 0x7c00u);
124
- mant >>= 13;
125
- return (uint16_t)(sign | 0x7c00u | mant | (mant == 0));
126
- }
127
-
128
- int32_t half_exp = (int32_t)exp - 127 + 15;
129
- if (half_exp >= 31)
130
- return (uint16_t)(sign | 0x7c00u);
131
-
132
- if (half_exp <= 0) {
133
- if (half_exp < -10)
134
- return (uint16_t)sign;
135
- mant |= 0x800000u;
136
- uint32_t shift = (uint32_t)(14 - half_exp);
137
- uint32_t rounded = (mant + (1u << (shift - 1))) >> shift;
138
- return (uint16_t)(sign | rounded);
139
- }
140
-
141
- mant += 0x1000u;
142
- if (mant & 0x800000u) {
143
- mant = 0;
144
- half_exp++;
145
- if (half_exp >= 31)
146
- return (uint16_t)(sign | 0x7c00u);
147
- }
148
-
149
- return (uint16_t)(sign | ((uint32_t)half_exp << 10) | (mant >> 13));
150
- }
151
-
152
- static float f16_bits_to_float(uint16_t half) {
153
- uint32_t sign = ((uint32_t)half & 0x8000u) << 16;
154
- uint32_t exp = ((uint32_t)half >> 10) & 0x1fu;
155
- uint32_t mant = (uint32_t)half & 0x03ffu;
156
- uint32_t bits;
157
-
158
- if (exp == 0) {
159
- if (mant == 0) {
160
- bits = sign;
161
- } else {
162
- exp = 1;
163
- while ((mant & 0x0400u) == 0) {
164
- mant <<= 1;
165
- exp--;
166
- }
167
- mant &= 0x03ffu;
168
- bits = sign | ((exp + (127 - 15)) << 23) | (mant << 13);
169
- }
170
- } else if (exp == 31) {
171
- bits = sign | 0x7f800000u | (mant << 13);
172
- } else {
173
- bits = sign | ((exp + (127 - 15)) << 23) | (mant << 13);
174
- }
175
-
176
- float value;
177
- memcpy(&value, &bits, sizeof(value));
178
- return value;
179
- }
180
-
181
- static void encode_f16_from_floats(uint8_t *dst, const float *src, size_t count) {
182
- for (size_t i = 0; i < count; i++)
183
- write_u16le(dst + i * 2, float_to_f16_bits(src[i]));
184
- }
185
-
186
- static void decode_f16_to_floats(float *dst, const uint8_t *src, size_t count) {
187
- for (size_t i = 0; i < count; i++)
188
- dst[i] = f16_bits_to_float(read_u16le(src + i * 2));
189
- }
190
-
191
- #if defined(SE_NEED_F16_LUT)
192
- static void init_f16_lut(void) {
193
- for (uint32_t i = 0; i <= 0xffffu; i++)
194
- se_f16_lut[i] = f16_bits_to_float((uint16_t)i);
195
- }
196
- #endif
197
-
198
- #if defined(SE_HAVE_X86_F16C_TARGET)
199
- #ifndef bit_OSXSAVE
200
- #define bit_OSXSAVE (1u << 27)
201
- #endif
202
- #ifndef bit_AVX
203
- #define bit_AVX (1u << 28)
204
- #endif
205
- #ifndef bit_F16C
206
- #define bit_F16C (1u << 29)
207
- #endif
208
-
209
- static int detect_x86_f16c(void) {
210
- unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0;
211
- if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx))
212
- return 0;
213
- if ((ecx & bit_OSXSAVE) == 0 || (ecx & bit_AVX) == 0 || (ecx & bit_F16C) == 0)
214
- return 0;
215
-
216
- uint32_t xcr0_lo = 0, xcr0_hi = 0;
217
- #if defined(_MSC_VER)
218
- return 0;
219
- #else
220
- __asm__ volatile("xgetbv" : "=a"(xcr0_lo), "=d"(xcr0_hi) : "c"(0));
221
- (void)xcr0_hi;
222
- return (xcr0_lo & 0x6u) == 0x6u;
223
- #endif
224
- }
225
- #endif
226
-
227
- static int checked_add_size(size_t a, size_t b, size_t *out) {
228
- if (a > SE_SIZE_MAX - b)
229
- return 0;
230
- *out = a + b;
231
- return 1;
232
- }
233
-
234
- static int checked_mul_size(size_t a, size_t b, size_t *out) {
235
- if (a != 0 && b > SE_SIZE_MAX / a)
236
- return 0;
237
- *out = a * b;
238
- return 1;
239
- }
240
-
241
- static int size_fits_long(size_t n) {
242
- return n <= (size_t)LONG_MAX;
243
- }
244
-
245
62
  static VALUE mStaticEmbeddings;
246
63
  static VALUE cModel;
247
64
  static VALUE cFiber;
@@ -266,6 +83,9 @@ static ID id_unk_count;
266
83
  static ID id_truncated;
267
84
  static ID id_dim;
268
85
  static ID id_allow_unfrozen;
86
+ static ID id_validate_encoding;
87
+ static ID id_full;
88
+ static ID id_prefix;
269
89
 
270
90
  RUBY_FUNC_EXPORTED void Init_static_embeddings(void);
271
91
 
@@ -327,7 +147,24 @@ static void raise_se(const se_error_t *err) {
327
147
  rb_raise(error_class_for(err->status), "%s", err->message);
328
148
  }
329
149
 
330
- static void check_text_encoding_at(VALUE str, long index) {
150
+ static VALUE se_simd_backend(VALUE self) {
151
+ (void)self;
152
+ switch (se_current_f16_backend()) {
153
+ case SE_F16_BACKEND_NEON_FP16:
154
+ return rb_str_new_cstr("neon-fp16");
155
+ case SE_F16_BACKEND_F16C:
156
+ return rb_str_new_cstr("f16c");
157
+ default:
158
+ return rb_str_new_cstr("lut");
159
+ }
160
+ }
161
+
162
+ typedef enum {
163
+ SE_VALIDATE_ENCODING_FULL = 0,
164
+ SE_VALIDATE_ENCODING_PREFIX = 1
165
+ } se_encoding_validation_t;
166
+
167
+ static void check_text_encoding_mode(VALUE str, long index, se_encoding_validation_t mode) {
331
168
  rb_encoding *enc = rb_enc_get(str);
332
169
  if (enc != utf8_encoding && enc != rb_usascii_encoding()) {
333
170
  if (index >= 0) {
@@ -338,7 +175,14 @@ static void check_text_encoding_at(VALUE str, long index) {
338
175
  rb_raise(eEncodingError, "expected UTF-8 or US-ASCII, got %s (transcode explicitly)",
339
176
  rb_enc_name(enc));
340
177
  }
341
- int cr = rb_enc_str_coderange(str);
178
+
179
+ int cr = ENC_CODERANGE(str);
180
+ if (cr == ENC_CODERANGE_UNKNOWN) {
181
+ if (mode == SE_VALIDATE_ENCODING_PREFIX)
182
+ return;
183
+ cr = rb_enc_str_coderange(str);
184
+ }
185
+
342
186
  if (cr != ENC_CODERANGE_VALID && cr != ENC_CODERANGE_7BIT) {
343
187
  if (index >= 0)
344
188
  rb_raise(eEncodingError, "input[%ld]: string is not valid %s", index, rb_enc_name(enc));
@@ -346,8 +190,19 @@ static void check_text_encoding_at(VALUE str, long index) {
346
190
  }
347
191
  }
348
192
 
349
- static void check_text_encoding(VALUE str) {
350
- check_text_encoding_at(str, -1);
193
+ static se_encoding_validation_t resolve_encoding_validation(VALUE opt) {
194
+ if (opt == Qundef || NIL_P(opt))
195
+ return SE_VALIDATE_ENCODING_FULL;
196
+
197
+ if (SYMBOL_P(opt)) {
198
+ ID sym = SYM2ID(opt);
199
+ if (sym == id_full)
200
+ return SE_VALIDATE_ENCODING_FULL;
201
+ if (sym == id_prefix)
202
+ return SE_VALIDATE_ENCODING_PREFIX;
203
+ }
204
+
205
+ rb_raise(rb_eArgError, "validate_encoding: must be :full or :prefix");
351
206
  }
352
207
 
353
208
  typedef struct {
@@ -648,6 +503,51 @@ static VALUE lookup_option(VALUE opts, ID id) {
648
503
  return rb_hash_lookup2(opts, ID2SYM(id), Qundef);
649
504
  }
650
505
 
506
+ typedef struct {
507
+ const ID *allowed;
508
+ size_t count;
509
+ } keyword_check_t;
510
+
511
+ static int reject_unknown_keyword_i(VALUE key, VALUE value, VALUE arg) {
512
+ const keyword_check_t *check = (const keyword_check_t *)arg;
513
+ (void)value;
514
+
515
+ if (!SYMBOL_P(key))
516
+ rb_raise(rb_eArgError, "keyword must be a Symbol");
517
+
518
+ ID id = SYM2ID(key);
519
+ for (size_t i = 0; i < check->count; i++) {
520
+ if (id == check->allowed[i])
521
+ return ST_CONTINUE;
522
+ }
523
+
524
+ VALUE names = rb_ary_new_capa((long)check->count);
525
+ for (size_t i = 0; i < check->count; i++)
526
+ rb_ary_push(names, rb_sprintf(":%s", rb_id2name(check->allowed[i])));
527
+
528
+ const char *name = rb_id2name(id);
529
+ rb_raise(rb_eArgError, "unknown keyword: :%s (accepted: %" PRIsVALUE ")", name ? name : "?",
530
+ rb_ary_join(names, rb_str_new_cstr(", ")));
531
+ }
532
+
533
+ static void check_keywords(VALUE opts, const ID *allowed, size_t count) {
534
+ if (NIL_P(opts))
535
+ return;
536
+ if (!RB_TYPE_P(opts, T_HASH))
537
+ rb_raise(rb_eArgError, "keywords must be a Hash");
538
+
539
+ keyword_check_t check;
540
+ check.allowed = allowed;
541
+ check.count = count;
542
+ rb_hash_foreach(opts, reject_unknown_keyword_i, (VALUE)&check);
543
+ }
544
+
545
+ #define SE_CHECK_KEYWORDS(opts, ...) \
546
+ do { \
547
+ const ID se_allowed_[] = {__VA_ARGS__}; \
548
+ check_keywords((opts), se_allowed_, sizeof(se_allowed_) / sizeof(se_allowed_[0])); \
549
+ } while (0)
550
+
651
551
  static void reject_parallel_threads(VALUE opts) {
652
552
  VALUE v = lookup_option(opts, id_threads);
653
553
  if (v == Qundef || v == Qnil)
@@ -719,12 +619,13 @@ static VALUE text_at(const text_source_t *src, size_t i) {
719
619
  return RARRAY_AREF(src->snapshot, (long)i);
720
620
  }
721
621
 
722
- static VALUE snapshot_texts(VALUE texts, int is_array, size_t count) {
622
+ static VALUE snapshot_texts(VALUE texts, int is_array, size_t count,
623
+ se_encoding_validation_t validation) {
723
624
  VALUE snapshot = rb_ary_new_capa((long)count);
724
625
  for (size_t i = 0; i < count; i++) {
725
626
  VALUE s = is_array ? rb_ary_entry(texts, (long)i) : texts;
726
627
  Check_Type(s, T_STRING);
727
- check_text_encoding_at(s, is_array ? (long)i : -1);
628
+ check_text_encoding_mode(s, is_array ? (long)i : -1, validation);
728
629
  rb_ary_push(snapshot, s);
729
630
  }
730
631
  return snapshot;
@@ -737,24 +638,26 @@ static int build_input_indexed(const text_source_t *src, const size_t *indices,
737
638
 
738
639
  size_t total = 0;
739
640
  for (size_t j = 0; j < count; j++) {
740
- if (!checked_add_size(total, copy_lens[j], &total))
641
+ if (!se_checked_add_size(total, copy_lens[j], &total))
741
642
  return 0;
742
643
  }
743
644
 
744
- size_t offsets_bytes;
745
- size_t lengths_bytes;
746
- size_t meta_bytes;
747
- size_t allocation_bytes;
748
- size_t slots = count ? count : 1;
749
-
750
- if (!checked_mul_size(slots, sizeof(size_t), &offsets_bytes) ||
751
- !checked_mul_size(slots, sizeof(size_t), &lengths_bytes) ||
752
- !checked_add_size(offsets_bytes, lengths_bytes, &meta_bytes) ||
753
- !checked_add_size(meta_bytes, total ? total : 1, &allocation_bytes)) {
645
+ size_t offsets_bytes = 0;
646
+ size_t lengths_bytes = 0;
647
+ size_t meta_bytes = 0;
648
+ size_t allocation_bytes = 0;
649
+ size_t payload_bytes = total;
650
+ size_t malloc_bytes = 0;
651
+
652
+ if (!se_array_bytes(count, sizeof(size_t), &offsets_bytes) ||
653
+ !se_array_bytes(count, sizeof(size_t), &lengths_bytes) ||
654
+ !se_checked_add_size(offsets_bytes, lengths_bytes, &meta_bytes) ||
655
+ !se_checked_add_size(meta_bytes, payload_bytes, &allocation_bytes) ||
656
+ !se_alloc_bytes(allocation_bytes, 1u, &malloc_bytes)) {
754
657
  return 0;
755
658
  }
756
659
 
757
- uint8_t *allocation = (uint8_t *)malloc(allocation_bytes);
660
+ uint8_t *allocation = (uint8_t *)se_malloc(SE_ALLOC_BATCH_INPUT, malloc_bytes);
758
661
  if (!allocation)
759
662
  return 0;
760
663
 
@@ -783,7 +686,7 @@ static int build_input_indexed(const text_source_t *src, const size_t *indices,
783
686
  }
784
687
 
785
688
  static void free_input(batch_input_t *input) {
786
- free(input->allocation);
689
+ se_free(input->allocation);
787
690
  memset(input, 0, sizeof(*input));
788
691
  }
789
692
 
@@ -798,8 +701,8 @@ static VALUE binary_string_create(VALUE arg) {
798
701
  }
799
702
 
800
703
  static VALUE binary_string_from_malloc(void *ptr, size_t bytes) {
801
- if (!size_fits_long(bytes)) {
802
- free(ptr);
704
+ if (!se_size_fits_long(bytes)) {
705
+ se_free(ptr);
803
706
  rb_raise(rb_eArgError, "embedding output is too large");
804
707
  }
805
708
 
@@ -809,7 +712,7 @@ static VALUE binary_string_from_malloc(void *ptr, size_t bytes) {
809
712
 
810
713
  int state = 0;
811
714
  VALUE result = rb_protect(binary_string_create, (VALUE)(uintptr_t)&job, &state);
812
- free(ptr);
715
+ se_free(ptr);
813
716
  if (state)
814
717
  rb_jump_tag(state);
815
718
  return result;
@@ -817,23 +720,17 @@ static VALUE binary_string_from_malloc(void *ptr, size_t bytes) {
817
720
 
818
721
  static VALUE binary_string_from_floats(float *ptr, size_t count, se_vector_format_t format) {
819
722
  size_t bytes;
820
- if (!checked_mul_size(count, vector_format_element_bytes(format), &bytes) ||
821
- !size_fits_long(bytes)) {
822
- free(ptr);
723
+ if (!se_checked_mul_size(count, se_vector_format_element_bytes(format), &bytes) ||
724
+ !se_size_fits_long(bytes)) {
725
+ se_free(ptr);
823
726
  rb_raise(rb_eArgError, "embedding output is too large");
824
727
  }
825
728
 
826
729
  if (format == SE_VECTOR_FORMAT_F32)
827
730
  return binary_string_from_malloc(ptr, bytes);
828
731
 
829
- uint8_t *encoded = (uint8_t *)malloc(bytes ? bytes : 1);
830
- if (!encoded) {
831
- free(ptr);
832
- rb_raise(rb_eNoMemError, "out of memory while encoding embedding output");
833
- }
834
- encode_f16_from_floats(encoded, ptr, count);
835
- free(ptr);
836
- return binary_string_from_malloc(encoded, bytes);
732
+ se_encode_f16_from_floats((uint8_t *)ptr, ptr, count);
733
+ return binary_string_from_malloc(ptr, bytes);
837
734
  }
838
735
 
839
736
  typedef struct {
@@ -844,17 +741,47 @@ typedef struct {
844
741
  size_t *copy_lens;
845
742
  } embed_run_t;
846
743
 
744
+ typedef enum {
745
+ EMBED_RUN_ALLOC_OK = 0,
746
+ EMBED_RUN_ALLOC_OVERFLOW,
747
+ EMBED_RUN_ALLOC_OOM
748
+ } embed_run_alloc_status_t;
749
+
847
750
  static void embed_run_free(embed_run_t *run) {
848
- free(run->out);
849
- free(run->stats);
850
- free(run->targets);
851
- free(run->pending);
852
- free(run->copy_lens);
751
+ se_free(run->out);
752
+ se_free(run->stats);
753
+ se_free(run->targets);
754
+ se_free(run->pending);
755
+ se_free(run->copy_lens);
853
756
  memset(run, 0, sizeof(*run));
854
757
  }
855
758
 
759
+ static embed_run_alloc_status_t embed_run_alloc(embed_run_t *run, size_t count, size_t floats) {
760
+ size_t out_bytes = 0;
761
+ size_t stats_bytes = 0;
762
+ size_t targets_bytes = 0;
763
+
764
+ memset(run, 0, sizeof(*run));
765
+ if (!se_alloc_bytes(floats, sizeof(float), &out_bytes) ||
766
+ !se_alloc_bytes(count, sizeof(se_token_stats_t), &stats_bytes) ||
767
+ !se_alloc_bytes(count, sizeof(size_t), &targets_bytes))
768
+ return EMBED_RUN_ALLOC_OVERFLOW;
769
+
770
+ run->out = (float *)se_calloc(SE_ALLOC_BATCH_OUTPUT, 1, out_bytes);
771
+ run->stats = (se_token_stats_t *)se_calloc(SE_ALLOC_BATCH_STATS, 1, stats_bytes);
772
+ run->targets = (size_t *)se_calloc(SE_ALLOC_BATCH_INDEX, 1, targets_bytes);
773
+ run->pending = (size_t *)se_calloc(SE_ALLOC_BATCH_INDEX, 1, targets_bytes);
774
+ run->copy_lens = (size_t *)se_calloc(SE_ALLOC_BATCH_INDEX, 1, targets_bytes);
775
+ if (run->out && run->stats && run->targets && run->pending && run->copy_lens)
776
+ return EMBED_RUN_ALLOC_OK;
777
+
778
+ embed_run_free(run);
779
+ return EMBED_RUN_ALLOC_OOM;
780
+ }
781
+
856
782
  static VALUE embed_texts_internal(VALUE self, VALUE texts, int is_array, size_t count,
857
783
  VALUE max_tokens_opt, se_vector_format_t format,
784
+ se_encoding_validation_t validation,
858
785
  se_token_stats_t *stats_out) {
859
786
  model_wrapper_t *w = get_model(self);
860
787
  const se_model_t *model = &w->model;
@@ -862,29 +789,23 @@ static VALUE embed_texts_internal(VALUE self, VALUE texts, int is_array, size_t
862
789
  const uint32_t max_tokens = resolve_max_tokens(model, max_tokens_opt);
863
790
 
864
791
  text_source_t source;
865
- source.snapshot = snapshot_texts(texts, is_array, count);
792
+ source.snapshot = snapshot_texts(texts, is_array, count, validation);
866
793
  source.is_array = is_array;
867
794
  const text_source_t *src = &source;
868
795
 
869
796
  size_t floats;
870
797
  size_t out_bytes;
871
- if (!checked_mul_size(count, dim, &floats) ||
872
- !checked_mul_size(floats, vector_format_element_bytes(format), &out_bytes) ||
873
- !size_fits_long(out_bytes))
798
+ if (!se_checked_mul_size(count, dim, &floats) ||
799
+ !se_checked_mul_size(floats, se_vector_format_element_bytes(format), &out_bytes) ||
800
+ !se_size_fits_long(out_bytes))
874
801
  rb_raise(rb_eArgError, "embedding output is too large");
875
802
 
876
803
  embed_run_t run;
877
- memset(&run, 0, sizeof(run));
878
- size_t slots = count ? count : 1;
879
- run.out = (float *)calloc(floats ? floats : 1, sizeof(float));
880
- run.stats = (se_token_stats_t *)calloc(slots, sizeof(se_token_stats_t));
881
- run.targets = (size_t *)calloc(slots, sizeof(size_t));
882
- run.pending = (size_t *)calloc(slots, sizeof(size_t));
883
- run.copy_lens = (size_t *)calloc(slots, sizeof(size_t));
884
- if (!run.out || !run.stats || !run.targets || !run.pending || !run.copy_lens) {
885
- embed_run_free(&run);
804
+ embed_run_alloc_status_t alloc_status = embed_run_alloc(&run, count, floats);
805
+ if (alloc_status == EMBED_RUN_ALLOC_OVERFLOW)
806
+ rb_raise(rb_eArgError, "embedding output is too large");
807
+ if (alloc_status == EMBED_RUN_ALLOC_OOM)
886
808
  rb_raise(rb_eNoMemError, "out of memory");
887
- }
888
809
 
889
810
  size_t initial = prefix_initial_target(max_tokens);
890
811
  size_t npending = count;
@@ -964,13 +885,13 @@ static VALUE embed_texts_internal(VALUE self, VALUE texts, int is_array, size_t
964
885
  if (stats_out && count)
965
886
  *stats_out = run.stats[0];
966
887
 
967
- free(run.stats);
888
+ se_free(run.stats);
968
889
  run.stats = NULL;
969
- free(run.targets);
890
+ se_free(run.targets);
970
891
  run.targets = NULL;
971
- free(run.pending);
892
+ se_free(run.pending);
972
893
  run.pending = NULL;
973
- free(run.copy_lens);
894
+ se_free(run.copy_lens);
974
895
  run.copy_lens = NULL;
975
896
 
976
897
  float *out = run.out;
@@ -982,39 +903,44 @@ static VALUE embed_texts_internal(VALUE self, VALUE texts, int is_array, size_t
982
903
  }
983
904
 
984
905
  static VALUE embed_batch_internal(VALUE self, VALUE texts, VALUE max_tokens_opt,
985
- se_vector_format_t format) {
906
+ se_vector_format_t format, se_encoding_validation_t validation) {
986
907
  Check_Type(texts, T_ARRAY);
987
908
  return embed_texts_internal(self, texts, 1, (size_t)RARRAY_LEN(texts), max_tokens_opt, format,
988
- NULL);
909
+ validation, NULL);
989
910
  }
990
911
 
991
912
  static VALUE model_embed_batch(int argc, VALUE *argv, VALUE self) {
992
913
  VALUE texts, opts;
993
914
  rb_scan_args(argc, argv, "1:", &texts, &opts);
915
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_format, id_validate_encoding, id_threads);
994
916
  reject_parallel_threads(opts);
995
917
 
996
918
  VALUE max_tokens = lookup_option(opts, id_max_tokens);
997
919
  se_vector_format_t format = resolve_vector_format(lookup_option(opts, id_format));
998
- return embed_batch_internal(self, texts, max_tokens, format);
920
+ se_encoding_validation_t validation =
921
+ resolve_encoding_validation(lookup_option(opts, id_validate_encoding));
922
+ return embed_batch_internal(self, texts, max_tokens, format, validation);
999
923
  }
1000
924
 
1001
925
  static VALUE embed_one_via_batch(VALUE self, VALUE text, VALUE max_tokens_opt,
1002
- se_vector_format_t format, se_token_stats_t *stats) {
1003
- return embed_texts_internal(self, text, 0, 1, max_tokens_opt, format, stats);
926
+ se_vector_format_t format, se_encoding_validation_t validation,
927
+ se_token_stats_t *stats) {
928
+ return embed_texts_internal(self, text, 0, 1, max_tokens_opt, format, validation, stats);
1004
929
  }
1005
930
 
1006
931
  static VALUE embed_one_value(VALUE self, VALUE text, VALUE max_tokens_opt,
1007
- se_vector_format_t format, se_token_stats_t *stats) {
932
+ se_vector_format_t format, se_encoding_validation_t validation,
933
+ se_token_stats_t *stats) {
1008
934
  model_wrapper_t *w = get_model(self);
1009
935
  Check_Type(text, T_STRING);
1010
- check_text_encoding(text);
936
+ check_text_encoding_mode(text, -1, validation);
1011
937
 
1012
938
  if (format != SE_VECTOR_FORMAT_F32 || (size_t)RSTRING_LEN(text) >= SE_GVL_UNLOCK_THRESHOLD)
1013
- return embed_one_via_batch(self, text, max_tokens_opt, format, stats);
939
+ return embed_one_via_batch(self, text, max_tokens_opt, format, validation, stats);
1014
940
 
1015
941
  const uint32_t dim = w->model.meta.dim;
1016
942
  size_t out_bytes;
1017
- if (!checked_mul_size(dim, sizeof(float), &out_bytes) || !size_fits_long(out_bytes))
943
+ if (!se_checked_mul_size(dim, sizeof(float), &out_bytes) || !se_size_fits_long(out_bytes))
1018
944
  rb_raise(rb_eArgError, "embedding output is too large");
1019
945
 
1020
946
  VALUE result = rb_str_new(NULL, (long)out_bytes);
@@ -1047,19 +973,25 @@ static VALUE embed_one_value(VALUE self, VALUE text, VALUE max_tokens_opt,
1047
973
  static VALUE model_embed(int argc, VALUE *argv, VALUE self) {
1048
974
  VALUE text, opts;
1049
975
  rb_scan_args(argc, argv, "1:", &text, &opts);
976
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_format, id_validate_encoding, id_threads);
1050
977
  reject_parallel_threads(opts);
1051
978
  return embed_one_value(self, text, lookup_option(opts, id_max_tokens),
1052
- resolve_vector_format(lookup_option(opts, id_format)), NULL);
979
+ resolve_vector_format(lookup_option(opts, id_format)),
980
+ resolve_encoding_validation(lookup_option(opts, id_validate_encoding)),
981
+ NULL);
1053
982
  }
1054
983
 
1055
984
  static VALUE model_embed_with_stats(int argc, VALUE *argv, VALUE self) {
1056
985
  VALUE text, opts;
1057
986
  rb_scan_args(argc, argv, "1:", &text, &opts);
987
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_format, id_validate_encoding, id_threads);
1058
988
  reject_parallel_threads(opts);
1059
989
 
1060
990
  se_token_stats_t stats;
1061
- VALUE vector = embed_one_value(self, text, lookup_option(opts, id_max_tokens),
1062
- resolve_vector_format(lookup_option(opts, id_format)), &stats);
991
+ VALUE vector = embed_one_value(
992
+ self, text, lookup_option(opts, id_max_tokens),
993
+ resolve_vector_format(lookup_option(opts, id_format)),
994
+ resolve_encoding_validation(lookup_option(opts, id_validate_encoding)), &stats);
1063
995
 
1064
996
  VALUE hash = rb_hash_new();
1065
997
  rb_hash_aset(hash, ID2SYM(id_vector), vector);
@@ -1072,10 +1004,12 @@ static VALUE model_embed_with_stats(int argc, VALUE *argv, VALUE self) {
1072
1004
  static VALUE model_tokenize(int argc, VALUE *argv, VALUE self) {
1073
1005
  VALUE text, opts;
1074
1006
  rb_scan_args(argc, argv, "1:", &text, &opts);
1007
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_validate_encoding);
1075
1008
 
1076
1009
  model_wrapper_t *w = get_model(self);
1077
1010
  Check_Type(text, T_STRING);
1078
- check_text_encoding(text);
1011
+ check_text_encoding_mode(
1012
+ text, -1, resolve_encoding_validation(lookup_option(opts, id_validate_encoding)));
1079
1013
 
1080
1014
  uint32_t max_tokens = resolve_max_tokens(&w->model, lookup_option(opts, id_max_tokens));
1081
1015
 
@@ -1183,7 +1117,11 @@ static VALUE embed_token_ids_body(VALUE arg) {
1183
1117
  run->ids[i] = (uint32_t)conv.value;
1184
1118
  }
1185
1119
 
1186
- run->out = (float *)calloc(run->model->meta.dim ? run->model->meta.dim : 1, sizeof(float));
1120
+ size_t out_bytes;
1121
+ if (!se_alloc_bytes(run->model->meta.dim, sizeof(float), &out_bytes))
1122
+ rb_raise(rb_eArgError, "embedding output is too large");
1123
+
1124
+ run->out = (float *)se_calloc(SE_ALLOC_BATCH_OUTPUT, 1, out_bytes);
1187
1125
  if (!run->out)
1188
1126
  rb_raise(rb_eNoMemError, "out of memory");
1189
1127
 
@@ -1214,7 +1152,7 @@ static VALUE embed_token_ids_body(VALUE arg) {
1214
1152
  if (run->stats_out)
1215
1153
  *run->stats_out = run->stats;
1216
1154
 
1217
- free(run->ids);
1155
+ se_free(run->ids);
1218
1156
  run->ids = NULL;
1219
1157
 
1220
1158
  float *out = run->out;
@@ -1224,8 +1162,8 @@ static VALUE embed_token_ids_body(VALUE arg) {
1224
1162
 
1225
1163
  static VALUE embed_token_ids_ensure(VALUE arg) {
1226
1164
  ids_run_t *run = (ids_run_t *)(uintptr_t)arg;
1227
- free(run->ids);
1228
- free(run->out);
1165
+ se_free(run->ids);
1166
+ se_free(run->out);
1229
1167
  run->ids = NULL;
1230
1168
  run->out = NULL;
1231
1169
  return Qnil;
@@ -1247,12 +1185,13 @@ static VALUE embed_token_ids_value(VALUE self, VALUE ids_value, VALUE max_tokens
1247
1185
  }
1248
1186
 
1249
1187
  size_t ids_bytes;
1250
- if (!checked_mul_size(n ? n : 1, sizeof(uint32_t), &ids_bytes))
1188
+ if (!se_alloc_bytes(n, sizeof(uint32_t), &ids_bytes))
1251
1189
  rb_raise(rb_eArgError, "token id array is too large");
1252
1190
 
1253
1191
  size_t out_bytes;
1254
- if (!checked_mul_size(w->model.meta.dim, vector_format_element_bytes(format), &out_bytes) ||
1255
- !size_fits_long(out_bytes))
1192
+ if (!se_checked_mul_size(w->model.meta.dim, se_vector_format_element_bytes(format),
1193
+ &out_bytes) ||
1194
+ !se_size_fits_long(out_bytes))
1256
1195
  rb_raise(rb_eArgError, "embedding output is too large");
1257
1196
 
1258
1197
  ids_run_t run;
@@ -1265,7 +1204,7 @@ static VALUE embed_token_ids_value(VALUE self, VALUE ids_value, VALUE max_tokens
1265
1204
  run.format = format;
1266
1205
  run.stats_out = stats_out;
1267
1206
  run.truncated = truncated;
1268
- run.ids = (uint32_t *)malloc(ids_bytes);
1207
+ run.ids = (uint32_t *)se_malloc(SE_ALLOC_TOKEN_IDS, ids_bytes);
1269
1208
  if (!run.ids)
1270
1209
  rb_raise(rb_eNoMemError, "out of memory");
1271
1210
 
@@ -1278,6 +1217,7 @@ static VALUE embed_token_ids_value(VALUE self, VALUE ids_value, VALUE max_tokens
1278
1217
  static VALUE model_embed_token_ids(int argc, VALUE *argv, VALUE self) {
1279
1218
  VALUE ids_value, opts;
1280
1219
  rb_scan_args(argc, argv, "1:", &ids_value, &opts);
1220
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_format, id_threads);
1281
1221
  reject_parallel_threads(opts);
1282
1222
  return embed_token_ids_value(self, ids_value, lookup_option(opts, id_max_tokens),
1283
1223
  resolve_vector_format(lookup_option(opts, id_format)), NULL);
@@ -1286,6 +1226,7 @@ static VALUE model_embed_token_ids(int argc, VALUE *argv, VALUE self) {
1286
1226
  static VALUE model_embed_token_ids_with_stats(int argc, VALUE *argv, VALUE self) {
1287
1227
  VALUE ids_value, opts;
1288
1228
  rb_scan_args(argc, argv, "1:", &ids_value, &opts);
1229
+ SE_CHECK_KEYWORDS(opts, id_max_tokens, id_format, id_threads);
1289
1230
  reject_parallel_threads(opts);
1290
1231
 
1291
1232
  se_token_stats_t stats;
@@ -1302,23 +1243,9 @@ static VALUE model_embed_token_ids_with_stats(int argc, VALUE *argv, VALUE self)
1302
1243
  return hash;
1303
1244
  }
1304
1245
 
1305
- typedef struct {
1306
- const float *q;
1307
- const void *m;
1308
- se_vector_format_t format;
1309
- size_t dim;
1310
- size_t rows;
1311
- long k;
1312
- size_t *best_idx;
1313
- float *best_score;
1314
- int cosine;
1315
- float inv_query_norm;
1316
- volatile sig_atomic_t cancelled;
1317
- } topk_job_t;
1318
-
1319
1246
  typedef struct {
1320
1247
  VALUE matrix;
1321
- topk_job_t job;
1248
+ se_topk_job_t job;
1322
1249
  float *q_copy;
1323
1250
  float *matrix_copy;
1324
1251
  size_t matrix_bytes;
@@ -1330,460 +1257,11 @@ static int ptr_is_float_aligned(const void *ptr) {
1330
1257
  }
1331
1258
 
1332
1259
  static void topk_unblock_cancel(void *arg) {
1333
- topk_job_t *job = (topk_job_t *)arg;
1260
+ se_topk_job_t *job = (se_topk_job_t *)arg;
1334
1261
  if (job)
1335
1262
  job->cancelled = 1;
1336
1263
  }
1337
1264
 
1338
- static float dot_product_unrolled(const float *q, const float *row, size_t dim) {
1339
- size_t j = 0;
1340
- #if defined(SE_HAVE_NEON)
1341
- float32x4_t a0 = vdupq_n_f32(0.0f);
1342
- float32x4_t a1 = vdupq_n_f32(0.0f);
1343
- float32x4_t a2 = vdupq_n_f32(0.0f);
1344
- float32x4_t a3 = vdupq_n_f32(0.0f);
1345
- for (; j + 15 < dim; j += 16) {
1346
- a0 = vmlaq_f32(a0, vld1q_f32(q + j), vld1q_f32(row + j));
1347
- a1 = vmlaq_f32(a1, vld1q_f32(q + j + 4), vld1q_f32(row + j + 4));
1348
- a2 = vmlaq_f32(a2, vld1q_f32(q + j + 8), vld1q_f32(row + j + 8));
1349
- a3 = vmlaq_f32(a3, vld1q_f32(q + j + 12), vld1q_f32(row + j + 12));
1350
- }
1351
- float32x4_t sumv = vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3));
1352
- #if defined(__aarch64__)
1353
- float dot = vaddvq_f32(sumv);
1354
- #else
1355
- float32x2_t pair = vadd_f32(vget_low_f32(sumv), vget_high_f32(sumv));
1356
- pair = vpadd_f32(pair, pair);
1357
- float dot = vget_lane_f32(pair, 0);
1358
- #endif
1359
- #elif defined(SE_HAVE_SSE)
1360
- __m128 a0 = _mm_setzero_ps();
1361
- __m128 a1 = _mm_setzero_ps();
1362
- __m128 a2 = _mm_setzero_ps();
1363
- __m128 a3 = _mm_setzero_ps();
1364
- for (; j + 15 < dim; j += 16) {
1365
- a0 = _mm_add_ps(a0, _mm_mul_ps(_mm_loadu_ps(q + j), _mm_loadu_ps(row + j)));
1366
- a1 = _mm_add_ps(a1, _mm_mul_ps(_mm_loadu_ps(q + j + 4), _mm_loadu_ps(row + j + 4)));
1367
- a2 = _mm_add_ps(a2, _mm_mul_ps(_mm_loadu_ps(q + j + 8), _mm_loadu_ps(row + j + 8)));
1368
- a3 = _mm_add_ps(a3, _mm_mul_ps(_mm_loadu_ps(q + j + 12), _mm_loadu_ps(row + j + 12)));
1369
- }
1370
- __m128 sumv = _mm_add_ps(_mm_add_ps(a0, a1), _mm_add_ps(a2, a3));
1371
- float tmp[4];
1372
- _mm_storeu_ps(tmp, sumv);
1373
- float dot = (tmp[0] + tmp[1]) + (tmp[2] + tmp[3]);
1374
- #else
1375
- float s0 = 0.0f;
1376
- float s1 = 0.0f;
1377
- float s2 = 0.0f;
1378
- float s3 = 0.0f;
1379
- float s4 = 0.0f;
1380
- float s5 = 0.0f;
1381
- float s6 = 0.0f;
1382
- float s7 = 0.0f;
1383
- for (; j + 7 < dim; j += 8) {
1384
- s0 += q[j] * row[j];
1385
- s1 += q[j + 1] * row[j + 1];
1386
- s2 += q[j + 2] * row[j + 2];
1387
- s3 += q[j + 3] * row[j + 3];
1388
- s4 += q[j + 4] * row[j + 4];
1389
- s5 += q[j + 5] * row[j + 5];
1390
- s6 += q[j + 6] * row[j + 6];
1391
- s7 += q[j + 7] * row[j + 7];
1392
- }
1393
- float dot = (s0 + s1) + (s2 + s3) + (s4 + s5) + (s6 + s7);
1394
- #endif
1395
- for (; j < dim; j++)
1396
- dot += q[j] * row[j];
1397
- return dot;
1398
- }
1399
-
1400
- static float dot_and_row_sq_unrolled(const float *q, const float *row, size_t dim,
1401
- float *row_sq_out) {
1402
- size_t j = 0;
1403
- #if defined(SE_HAVE_NEON)
1404
- float32x4_t d0 = vdupq_n_f32(0.0f);
1405
- float32x4_t d1 = vdupq_n_f32(0.0f);
1406
- float32x4_t d2 = vdupq_n_f32(0.0f);
1407
- float32x4_t d3 = vdupq_n_f32(0.0f);
1408
- float32x4_t s0 = vdupq_n_f32(0.0f);
1409
- float32x4_t s1 = vdupq_n_f32(0.0f);
1410
- float32x4_t s2 = vdupq_n_f32(0.0f);
1411
- float32x4_t s3 = vdupq_n_f32(0.0f);
1412
- for (; j + 15 < dim; j += 16) {
1413
- float32x4_t q0 = vld1q_f32(q + j);
1414
- float32x4_t r0 = vld1q_f32(row + j);
1415
- float32x4_t q1 = vld1q_f32(q + j + 4);
1416
- float32x4_t r1 = vld1q_f32(row + j + 4);
1417
- float32x4_t q2 = vld1q_f32(q + j + 8);
1418
- float32x4_t r2 = vld1q_f32(row + j + 8);
1419
- float32x4_t q3 = vld1q_f32(q + j + 12);
1420
- float32x4_t r3 = vld1q_f32(row + j + 12);
1421
- d0 = vmlaq_f32(d0, q0, r0);
1422
- d1 = vmlaq_f32(d1, q1, r1);
1423
- d2 = vmlaq_f32(d2, q2, r2);
1424
- d3 = vmlaq_f32(d3, q3, r3);
1425
- s0 = vmlaq_f32(s0, r0, r0);
1426
- s1 = vmlaq_f32(s1, r1, r1);
1427
- s2 = vmlaq_f32(s2, r2, r2);
1428
- s3 = vmlaq_f32(s3, r3, r3);
1429
- }
1430
- float32x4_t dotv = vaddq_f32(vaddq_f32(d0, d1), vaddq_f32(d2, d3));
1431
- float32x4_t sqv = vaddq_f32(vaddq_f32(s0, s1), vaddq_f32(s2, s3));
1432
- #if defined(__aarch64__)
1433
- float dot = vaddvq_f32(dotv);
1434
- float row_sq = vaddvq_f32(sqv);
1435
- #else
1436
- float32x2_t pair = vadd_f32(vget_low_f32(dotv), vget_high_f32(dotv));
1437
- pair = vpadd_f32(pair, pair);
1438
- float dot = vget_lane_f32(pair, 0);
1439
- pair = vadd_f32(vget_low_f32(sqv), vget_high_f32(sqv));
1440
- pair = vpadd_f32(pair, pair);
1441
- float row_sq = vget_lane_f32(pair, 0);
1442
- #endif
1443
- #elif defined(SE_HAVE_SSE)
1444
- __m128 d0 = _mm_setzero_ps();
1445
- __m128 d1 = _mm_setzero_ps();
1446
- __m128 d2 = _mm_setzero_ps();
1447
- __m128 d3 = _mm_setzero_ps();
1448
- __m128 s0 = _mm_setzero_ps();
1449
- __m128 s1 = _mm_setzero_ps();
1450
- __m128 s2 = _mm_setzero_ps();
1451
- __m128 s3 = _mm_setzero_ps();
1452
- for (; j + 15 < dim; j += 16) {
1453
- __m128 q0 = _mm_loadu_ps(q + j);
1454
- __m128 r0 = _mm_loadu_ps(row + j);
1455
- __m128 q1 = _mm_loadu_ps(q + j + 4);
1456
- __m128 r1 = _mm_loadu_ps(row + j + 4);
1457
- __m128 q2 = _mm_loadu_ps(q + j + 8);
1458
- __m128 r2 = _mm_loadu_ps(row + j + 8);
1459
- __m128 q3 = _mm_loadu_ps(q + j + 12);
1460
- __m128 r3 = _mm_loadu_ps(row + j + 12);
1461
- d0 = _mm_add_ps(d0, _mm_mul_ps(q0, r0));
1462
- d1 = _mm_add_ps(d1, _mm_mul_ps(q1, r1));
1463
- d2 = _mm_add_ps(d2, _mm_mul_ps(q2, r2));
1464
- d3 = _mm_add_ps(d3, _mm_mul_ps(q3, r3));
1465
- s0 = _mm_add_ps(s0, _mm_mul_ps(r0, r0));
1466
- s1 = _mm_add_ps(s1, _mm_mul_ps(r1, r1));
1467
- s2 = _mm_add_ps(s2, _mm_mul_ps(r2, r2));
1468
- s3 = _mm_add_ps(s3, _mm_mul_ps(r3, r3));
1469
- }
1470
- __m128 dotv = _mm_add_ps(_mm_add_ps(d0, d1), _mm_add_ps(d2, d3));
1471
- __m128 sqv = _mm_add_ps(_mm_add_ps(s0, s1), _mm_add_ps(s2, s3));
1472
- float tmp[4];
1473
- _mm_storeu_ps(tmp, dotv);
1474
- float dot = (tmp[0] + tmp[1]) + (tmp[2] + tmp[3]);
1475
- _mm_storeu_ps(tmp, sqv);
1476
- float row_sq = (tmp[0] + tmp[1]) + (tmp[2] + tmp[3]);
1477
- #else
1478
- float d0 = 0.0f;
1479
- float d1 = 0.0f;
1480
- float d2 = 0.0f;
1481
- float d3 = 0.0f;
1482
- float s0 = 0.0f;
1483
- float s1 = 0.0f;
1484
- float s2 = 0.0f;
1485
- float s3 = 0.0f;
1486
- for (; j + 3 < dim; j += 4) {
1487
- float r0 = row[j];
1488
- float r1 = row[j + 1];
1489
- float r2 = row[j + 2];
1490
- float r3 = row[j + 3];
1491
- d0 += q[j] * r0;
1492
- d1 += q[j + 1] * r1;
1493
- d2 += q[j + 2] * r2;
1494
- d3 += q[j + 3] * r3;
1495
- s0 += r0 * r0;
1496
- s1 += r1 * r1;
1497
- s2 += r2 * r2;
1498
- s3 += r3 * r3;
1499
- }
1500
- float dot = (d0 + d1) + (d2 + d3);
1501
- float row_sq = (s0 + s1) + (s2 + s3);
1502
- #endif
1503
- for (; j < dim; j++) {
1504
- float r = row[j];
1505
- dot += q[j] * r;
1506
- row_sq += r * r;
1507
- }
1508
- *row_sq_out = row_sq;
1509
- return dot;
1510
- }
1511
-
1512
- #if defined(SE_NEED_F16_LUT)
1513
- static float dot_product_f16_lut(const float *q, const uint8_t *row, size_t dim) {
1514
- size_t j = 0;
1515
- float s0 = 0.0f;
1516
- float s1 = 0.0f;
1517
- float s2 = 0.0f;
1518
- float s3 = 0.0f;
1519
- for (; j + 3 < dim; j += 4) {
1520
- s0 += q[j] * se_f16_lut[read_u16le(row + j * 2)];
1521
- s1 += q[j + 1] * se_f16_lut[read_u16le(row + (j + 1) * 2)];
1522
- s2 += q[j + 2] * se_f16_lut[read_u16le(row + (j + 2) * 2)];
1523
- s3 += q[j + 3] * se_f16_lut[read_u16le(row + (j + 3) * 2)];
1524
- }
1525
- float dot = (s0 + s1) + (s2 + s3);
1526
- for (; j < dim; j++)
1527
- dot += q[j] * se_f16_lut[read_u16le(row + j * 2)];
1528
- return dot;
1529
- }
1530
-
1531
- static float dot_and_row_sq_f16_lut(const float *q, const uint8_t *row, size_t dim,
1532
- float *row_sq_out) {
1533
- size_t j = 0;
1534
- float d0 = 0.0f;
1535
- float d1 = 0.0f;
1536
- float d2 = 0.0f;
1537
- float d3 = 0.0f;
1538
- float s0 = 0.0f;
1539
- float s1 = 0.0f;
1540
- float s2 = 0.0f;
1541
- float s3 = 0.0f;
1542
- for (; j + 3 < dim; j += 4) {
1543
- float r0 = se_f16_lut[read_u16le(row + j * 2)];
1544
- float r1 = se_f16_lut[read_u16le(row + (j + 1) * 2)];
1545
- float r2 = se_f16_lut[read_u16le(row + (j + 2) * 2)];
1546
- float r3 = se_f16_lut[read_u16le(row + (j + 3) * 2)];
1547
- d0 += q[j] * r0;
1548
- d1 += q[j + 1] * r1;
1549
- d2 += q[j + 2] * r2;
1550
- d3 += q[j + 3] * r3;
1551
- s0 += r0 * r0;
1552
- s1 += r1 * r1;
1553
- s2 += r2 * r2;
1554
- s3 += r3 * r3;
1555
- }
1556
- float dot = (d0 + d1) + (d2 + d3);
1557
- float row_sq = (s0 + s1) + (s2 + s3);
1558
- for (; j < dim; j++) {
1559
- float r = se_f16_lut[read_u16le(row + j * 2)];
1560
- dot += q[j] * r;
1561
- row_sq += r * r;
1562
- }
1563
- *row_sq_out = row_sq;
1564
- return dot;
1565
- }
1566
- #endif /* SE_NEED_F16_LUT */
1567
-
1568
- #if defined(SE_HAVE_NEON_FP16)
1569
- static float dot_product_f16_neon(const float *q, const uint8_t *row, size_t dim) {
1570
- size_t j = 0;
1571
- float32x4_t a0 = vdupq_n_f32(0.0f);
1572
- float32x4_t a1 = vdupq_n_f32(0.0f);
1573
- for (; j + 7 < dim; j += 8) {
1574
- float16x4_t h0 =
1575
- vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + j * 2)));
1576
- float16x4_t h1 =
1577
- vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + (j + 4) * 2)));
1578
- float32x4_t r0 = vcvt_f32_f16(h0);
1579
- float32x4_t r1 = vcvt_f32_f16(h1);
1580
- a0 = vmlaq_f32(a0, vld1q_f32(q + j), r0);
1581
- a1 = vmlaq_f32(a1, vld1q_f32(q + j + 4), r1);
1582
- }
1583
- float32x4_t sumv = vaddq_f32(a0, a1);
1584
- float dot = vaddvq_f32(sumv);
1585
- for (; j < dim; j++)
1586
- dot += q[j] * f16_bits_to_float(read_u16le(row + j * 2));
1587
- return dot;
1588
- }
1589
-
1590
- static float dot_and_row_sq_f16_neon(const float *q, const uint8_t *row, size_t dim,
1591
- float *row_sq_out) {
1592
- size_t j = 0;
1593
- float32x4_t d0 = vdupq_n_f32(0.0f);
1594
- float32x4_t d1 = vdupq_n_f32(0.0f);
1595
- float32x4_t s0 = vdupq_n_f32(0.0f);
1596
- float32x4_t s1 = vdupq_n_f32(0.0f);
1597
- for (; j + 7 < dim; j += 8) {
1598
- float16x4_t h0 =
1599
- vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + j * 2)));
1600
- float16x4_t h1 =
1601
- vreinterpret_f16_u16(vld1_u16((const uint16_t *)(const void *)(row + (j + 4) * 2)));
1602
- float32x4_t r0 = vcvt_f32_f16(h0);
1603
- float32x4_t r1 = vcvt_f32_f16(h1);
1604
- d0 = vmlaq_f32(d0, vld1q_f32(q + j), r0);
1605
- d1 = vmlaq_f32(d1, vld1q_f32(q + j + 4), r1);
1606
- s0 = vmlaq_f32(s0, r0, r0);
1607
- s1 = vmlaq_f32(s1, r1, r1);
1608
- }
1609
- float dot = vaddvq_f32(vaddq_f32(d0, d1));
1610
- float row_sq = vaddvq_f32(vaddq_f32(s0, s1));
1611
- for (; j < dim; j++) {
1612
- float r = f16_bits_to_float(read_u16le(row + j * 2));
1613
- dot += q[j] * r;
1614
- row_sq += r * r;
1615
- }
1616
- *row_sq_out = row_sq;
1617
- return dot;
1618
- }
1619
- #endif
1620
-
1621
- #if defined(SE_HAVE_X86_F16C_TARGET)
1622
- __attribute__((target("f16c,avx"))) static float hsum256_f16c(__m256 v) {
1623
- __m128 low = _mm256_castps256_ps128(v);
1624
- __m128 high = _mm256_extractf128_ps(v, 1);
1625
- __m128 sum = _mm_add_ps(low, high);
1626
- float tmp[4];
1627
- _mm_storeu_ps(tmp, sum);
1628
- return (tmp[0] + tmp[1]) + (tmp[2] + tmp[3]);
1629
- }
1630
-
1631
- __attribute__((target("f16c,avx"))) static float
1632
- dot_product_f16_f16c(const float *q, const uint8_t *row, size_t dim) {
1633
- size_t j = 0;
1634
- __m256 a0 = _mm256_setzero_ps();
1635
- __m256 a1 = _mm256_setzero_ps();
1636
- for (; j + 15 < dim; j += 16) {
1637
- __m256 r0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + j * 2)));
1638
- __m256 r1 =
1639
- _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + (j + 8) * 2)));
1640
- a0 = _mm256_add_ps(a0, _mm256_mul_ps(_mm256_loadu_ps(q + j), r0));
1641
- a1 = _mm256_add_ps(a1, _mm256_mul_ps(_mm256_loadu_ps(q + j + 8), r1));
1642
- }
1643
- float dot = hsum256_f16c(_mm256_add_ps(a0, a1));
1644
- for (; j + 7 < dim; j += 8) {
1645
- __m256 r = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + j * 2)));
1646
- dot += hsum256_f16c(_mm256_mul_ps(_mm256_loadu_ps(q + j), r));
1647
- }
1648
- for (; j < dim; j++)
1649
- dot += q[j] * f16_bits_to_float(read_u16le(row + j * 2));
1650
- return dot;
1651
- }
1652
-
1653
- __attribute__((target("f16c,avx"))) static float
1654
- dot_and_row_sq_f16_f16c(const float *q, const uint8_t *row, size_t dim, float *row_sq_out) {
1655
- size_t j = 0;
1656
- __m256 d0 = _mm256_setzero_ps();
1657
- __m256 d1 = _mm256_setzero_ps();
1658
- __m256 s0 = _mm256_setzero_ps();
1659
- __m256 s1 = _mm256_setzero_ps();
1660
- for (; j + 15 < dim; j += 16) {
1661
- __m256 r0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + j * 2)));
1662
- __m256 r1 =
1663
- _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + (j + 8) * 2)));
1664
- d0 = _mm256_add_ps(d0, _mm256_mul_ps(_mm256_loadu_ps(q + j), r0));
1665
- d1 = _mm256_add_ps(d1, _mm256_mul_ps(_mm256_loadu_ps(q + j + 8), r1));
1666
- s0 = _mm256_add_ps(s0, _mm256_mul_ps(r0, r0));
1667
- s1 = _mm256_add_ps(s1, _mm256_mul_ps(r1, r1));
1668
- }
1669
- float dot = hsum256_f16c(_mm256_add_ps(d0, d1));
1670
- float row_sq = hsum256_f16c(_mm256_add_ps(s0, s1));
1671
- for (; j + 7 < dim; j += 8) {
1672
- __m256 r = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *)(const void *)(row + j * 2)));
1673
- dot += hsum256_f16c(_mm256_mul_ps(_mm256_loadu_ps(q + j), r));
1674
- row_sq += hsum256_f16c(_mm256_mul_ps(r, r));
1675
- }
1676
- for (; j < dim; j++) {
1677
- float r = f16_bits_to_float(read_u16le(row + j * 2));
1678
- dot += q[j] * r;
1679
- row_sq += r * r;
1680
- }
1681
- *row_sq_out = row_sq;
1682
- return dot;
1683
- }
1684
- #endif
1685
-
1686
- static float dot_product_f16(const float *q, const uint8_t *row, size_t dim) {
1687
- #if defined(SE_HAVE_NEON_FP16)
1688
- return dot_product_f16_neon(q, row, dim);
1689
- #else
1690
- #if defined(SE_HAVE_X86_F16C_TARGET)
1691
- if (se_f16_backend == SE_F16_BACKEND_F16C)
1692
- return dot_product_f16_f16c(q, row, dim);
1693
- #endif
1694
- return dot_product_f16_lut(q, row, dim);
1695
- #endif
1696
- }
1697
-
1698
- static float dot_and_row_sq_f16(const float *q, const uint8_t *row, size_t dim, float *row_sq_out) {
1699
- #if defined(SE_HAVE_NEON_FP16)
1700
- return dot_and_row_sq_f16_neon(q, row, dim, row_sq_out);
1701
- #else
1702
- #if defined(SE_HAVE_X86_F16C_TARGET)
1703
- if (se_f16_backend == SE_F16_BACKEND_F16C)
1704
- return dot_and_row_sq_f16_f16c(q, row, dim, row_sq_out);
1705
- #endif
1706
- return dot_and_row_sq_f16_lut(q, row, dim, row_sq_out);
1707
- #endif
1708
- }
1709
-
1710
- static void select_f16_backend(void) {
1711
- #if defined(SE_HAVE_NEON_FP16)
1712
- se_f16_backend = SE_F16_BACKEND_NEON_FP16;
1713
- #else
1714
- #if defined(SE_HAVE_X86_F16C_TARGET)
1715
- if (detect_x86_f16c()) {
1716
- se_f16_backend = SE_F16_BACKEND_F16C;
1717
- return;
1718
- }
1719
- #endif
1720
- se_f16_backend = SE_F16_BACKEND_LUT;
1721
- init_f16_lut();
1722
- #endif
1723
- }
1724
-
1725
- static VALUE se_simd_backend(VALUE self) {
1726
- (void)self;
1727
- switch (se_f16_backend) {
1728
- case SE_F16_BACKEND_NEON_FP16:
1729
- return rb_str_new_cstr("neon-fp16");
1730
- case SE_F16_BACKEND_F16C:
1731
- return rb_str_new_cstr("f16c");
1732
- default:
1733
- return rb_str_new_cstr("lut");
1734
- }
1735
- }
1736
-
1737
- static float cosine_score(float dot, float row_sq, float inv_query_norm) {
1738
- if (row_sq > 0.0f)
1739
- return dot * inv_query_norm / sqrtf(row_sq);
1740
- if (row_sq == 0.0f)
1741
- return 0.0f;
1742
- return NAN;
1743
- }
1744
-
1745
- static void *topk_execute(void *arg) {
1746
- topk_job_t *job = (topk_job_t *)arg;
1747
- for (size_t r = 0; r < job->rows; r++) {
1748
- if ((r & 1023u) == 0 && job->cancelled)
1749
- return NULL;
1750
- float score;
1751
-
1752
- if (job->format == SE_VECTOR_FORMAT_F16) {
1753
- const uint8_t *row = (const uint8_t *)job->m + r * job->dim * 2u;
1754
- if (job->cosine) {
1755
- float row_sq = 0.0f;
1756
- score = dot_and_row_sq_f16(job->q, row, job->dim, &row_sq);
1757
- score = cosine_score(score, row_sq, job->inv_query_norm);
1758
- } else {
1759
- score = dot_product_f16(job->q, row, job->dim);
1760
- }
1761
- } else {
1762
- const float *row = (const float *)job->m + r * job->dim;
1763
- if (job->cosine) {
1764
- float row_sq = 0.0f;
1765
- score = dot_and_row_sq_unrolled(job->q, row, job->dim, &row_sq);
1766
- score = cosine_score(score, row_sq, job->inv_query_norm);
1767
- } else {
1768
- score = dot_product_unrolled(job->q, row, job->dim);
1769
- }
1770
- }
1771
-
1772
- if (!(score > job->best_score[job->k - 1]))
1773
- continue;
1774
-
1775
- long pos = job->k - 1;
1776
- while (pos > 0 && job->best_score[pos - 1] < score) {
1777
- job->best_score[pos] = job->best_score[pos - 1];
1778
- job->best_idx[pos] = job->best_idx[pos - 1];
1779
- pos--;
1780
- }
1781
- job->best_score[pos] = score;
1782
- job->best_idx[pos] = r;
1783
- }
1784
- return NULL;
1785
- }
1786
-
1787
1265
  static VALUE topk_body(VALUE arg) {
1788
1266
  topk_run_t *run = (topk_run_t *)(uintptr_t)arg;
1789
1267
  const char *matrix_ptr = RSTRING_PTR(run->matrix);
@@ -1796,17 +1274,17 @@ static VALUE topk_body(VALUE arg) {
1796
1274
  }
1797
1275
 
1798
1276
  if (run->job.cosine) {
1799
- float query_sq = dot_product_unrolled(run->job.q, run->job.q, run->job.dim);
1277
+ float query_sq = se_dot_product_f32(run->job.q, run->job.q, run->job.dim);
1800
1278
  if (!(query_sq > 0.0f))
1801
1279
  rb_raise(rb_eArgError, "cosine_top_k needs a query with a non-zero norm");
1802
1280
  run->job.inv_query_norm = 1.0f / sqrtf(query_sq);
1803
1281
  }
1804
1282
 
1805
1283
  if (run->release_gvl) {
1806
- rb_thread_call_without_gvl(topk_execute, &run->job, topk_unblock_cancel, &run->job);
1284
+ rb_thread_call_without_gvl(se_topk_execute, &run->job, topk_unblock_cancel, &run->job);
1807
1285
  rb_thread_check_ints();
1808
1286
  } else {
1809
- topk_execute(&run->job);
1287
+ se_topk_execute(&run->job);
1810
1288
  rb_thread_check_ints();
1811
1289
  }
1812
1290
 
@@ -1827,10 +1305,10 @@ static VALUE topk_body(VALUE arg) {
1827
1305
 
1828
1306
  static VALUE topk_ensure(VALUE arg) {
1829
1307
  topk_run_t *run = (topk_run_t *)(uintptr_t)arg;
1830
- free(run->q_copy);
1831
- free(run->matrix_copy);
1832
- free(run->job.best_idx);
1833
- free(run->job.best_score);
1308
+ se_free(run->q_copy);
1309
+ se_free(run->matrix_copy);
1310
+ se_free(run->job.best_idx);
1311
+ se_free(run->job.best_score);
1834
1312
  run->q_copy = NULL;
1835
1313
  run->matrix_copy = NULL;
1836
1314
  run->job.best_idx = NULL;
@@ -1888,17 +1366,18 @@ static void topk_check_matrix(VALUE matrix, size_t matrix_bytes, se_vector_forma
1888
1366
  static VALUE top_k_impl(int argc, VALUE *argv, VALUE self, int cosine) {
1889
1367
  VALUE query, matrix, k_val, opts;
1890
1368
  rb_scan_args(argc, argv, "3:", &query, &matrix, &k_val, &opts);
1369
+ SE_CHECK_KEYWORDS(opts, id_dim, id_format, id_allow_unfrozen);
1891
1370
  (void)self;
1892
1371
 
1893
1372
  Check_Type(query, T_STRING);
1894
1373
  Check_Type(matrix, T_STRING);
1895
1374
 
1896
1375
  se_vector_format_t format = resolve_vector_format(lookup_option(opts, id_format));
1897
- size_t element_bytes = vector_format_element_bytes(format);
1376
+ size_t element_bytes = se_vector_format_element_bytes(format);
1898
1377
  size_t dim = topk_required_dim(opts);
1899
1378
 
1900
1379
  size_t row_bytes;
1901
- if (!checked_mul_size(dim, element_bytes, &row_bytes) || !size_fits_long(row_bytes))
1380
+ if (!se_checked_mul_size(dim, element_bytes, &row_bytes) || !se_size_fits_long(row_bytes))
1902
1381
  rb_raise(rb_eArgError, "dim: is too large");
1903
1382
 
1904
1383
  if ((size_t)RSTRING_LEN(query) != row_bytes)
@@ -1938,14 +1417,22 @@ static VALUE top_k_impl(int argc, VALUE *argv, VALUE self, int cosine) {
1938
1417
  run.job.inv_query_norm = 1.0f;
1939
1418
 
1940
1419
  size_t q_float_bytes;
1941
- if (!checked_mul_size(dim, sizeof(float), &q_float_bytes))
1420
+ if (!se_checked_mul_size(dim, sizeof(float), &q_float_bytes))
1942
1421
  rb_raise(rb_eArgError, "dim: is too large");
1943
1422
 
1944
- run.q_copy = (float *)malloc(q_float_bytes);
1945
- run.job.best_idx = (size_t *)calloc((size_t)k, sizeof(size_t));
1946
- run.job.best_score = (float *)malloc((size_t)k * sizeof(float));
1423
+ run.q_copy = (float *)se_malloc(SE_ALLOC_TOPK_QUERY, q_float_bytes);
1424
+ size_t best_idx_bytes = 0;
1425
+ size_t best_score_bytes = 0;
1426
+ if (!se_array_bytes((size_t)k, sizeof(size_t), &best_idx_bytes) ||
1427
+ !se_array_bytes((size_t)k, sizeof(float), &best_score_bytes)) {
1428
+ topk_ensure((VALUE)(uintptr_t)&run);
1429
+ rb_raise(rb_eArgError, "k is too large");
1430
+ }
1431
+
1432
+ run.job.best_idx = (size_t *)se_calloc(SE_ALLOC_TOPK_BEST, 1, best_idx_bytes);
1433
+ run.job.best_score = (float *)se_malloc(SE_ALLOC_TOPK_BEST, best_score_bytes);
1947
1434
  if (needs_copy)
1948
- run.matrix_copy = (float *)malloc(matrix_bytes ? matrix_bytes : 1);
1435
+ run.matrix_copy = (float *)se_malloc(SE_ALLOC_TOPK_MATRIX_COPY, matrix_bytes);
1949
1436
  if (!run.q_copy || !run.job.best_idx || !run.job.best_score ||
1950
1437
  (needs_copy && !run.matrix_copy)) {
1951
1438
  topk_ensure((VALUE)(uintptr_t)&run);
@@ -1953,7 +1440,7 @@ static VALUE top_k_impl(int argc, VALUE *argv, VALUE self, int cosine) {
1953
1440
  }
1954
1441
 
1955
1442
  if (format == SE_VECTOR_FORMAT_F16)
1956
- decode_f16_to_floats(run.q_copy, (const uint8_t *)RSTRING_PTR(query), dim);
1443
+ se_decode_f16_to_floats(run.q_copy, (const uint8_t *)RSTRING_PTR(query), dim);
1957
1444
  else
1958
1445
  memcpy(run.q_copy, RSTRING_PTR(query), row_bytes);
1959
1446
  run.job.q = run.q_copy;
@@ -1982,14 +1469,14 @@ static VALUE se_encode_f16(VALUE self, VALUE ary) {
1982
1469
 
1983
1470
  long n = RARRAY_LEN(ary);
1984
1471
  size_t bytes;
1985
- if (!checked_mul_size((size_t)n, 2, &bytes) || !size_fits_long(bytes))
1472
+ if (!se_checked_mul_size((size_t)n, 2, &bytes) || !se_size_fits_long(bytes))
1986
1473
  rb_raise(rb_eArgError, "vector is too large");
1987
1474
 
1988
1475
  VALUE out = rb_str_new(NULL, (long)bytes);
1989
1476
  rb_enc_associate(out, binary_encoding);
1990
1477
  for (long i = 0; i < n; i++) {
1991
1478
  double v = NUM2DBL(rb_ary_entry(ary, i));
1992
- write_u16le((uint8_t *)RSTRING_PTR(out) + (size_t)i * 2, float_to_f16_bits((float)v));
1479
+ se_write_f16le((uint8_t *)RSTRING_PTR(out) + (size_t)i * 2, (float)v);
1993
1480
  }
1994
1481
  return out;
1995
1482
  }
@@ -2005,14 +1492,52 @@ static VALUE se_decode_f16(VALUE self, VALUE blob) {
2005
1492
  VALUE out = rb_ary_new_capa(n / 2);
2006
1493
  for (long i = 0; i < n / 2; i++) {
2007
1494
  const uint8_t *src = (const uint8_t *)RSTRING_PTR(blob) + (size_t)i * 2;
2008
- rb_ary_push(out, DBL2NUM((double)f16_bits_to_float(read_u16le(src))));
1495
+ rb_ary_push(out, DBL2NUM((double)se_read_f16le(src)));
2009
1496
  }
2010
1497
  RB_GC_GUARD(blob);
2011
1498
  return out;
2012
1499
  }
2013
1500
 
1501
+ #if SE_ENABLE_ALLOC_STATS
1502
+ static VALUE se_alloc_stats_hash(VALUE self) {
1503
+ (void)self;
1504
+ se_alloc_stats_t stats[SE_ALLOC_CATEGORY_COUNT];
1505
+ se_alloc_stats_snapshot(stats);
1506
+
1507
+ VALUE out = rb_hash_new();
1508
+ ID id_current_bytes = rb_intern("current_bytes");
1509
+ ID id_peak_bytes = rb_intern("peak_bytes");
1510
+ ID id_total_allocated_bytes = rb_intern("total_allocated_bytes");
1511
+ ID id_total_freed_bytes = rb_intern("total_freed_bytes");
1512
+ ID id_alloc_count = rb_intern("alloc_count");
1513
+ ID id_realloc_count = rb_intern("realloc_count");
1514
+ ID id_free_count = rb_intern("free_count");
1515
+
1516
+ for (int i = 0; i < SE_ALLOC_CATEGORY_COUNT; i++) {
1517
+ VALUE item = rb_hash_new();
1518
+ rb_hash_aset(item, ID2SYM(id_current_bytes), SIZET2NUM(stats[i].current_bytes));
1519
+ rb_hash_aset(item, ID2SYM(id_peak_bytes), SIZET2NUM(stats[i].peak_bytes));
1520
+ rb_hash_aset(item, ID2SYM(id_total_allocated_bytes),
1521
+ SIZET2NUM(stats[i].total_allocated_bytes));
1522
+ rb_hash_aset(item, ID2SYM(id_total_freed_bytes), SIZET2NUM(stats[i].total_freed_bytes));
1523
+ rb_hash_aset(item, ID2SYM(id_alloc_count), SIZET2NUM(stats[i].alloc_count));
1524
+ rb_hash_aset(item, ID2SYM(id_realloc_count), SIZET2NUM(stats[i].realloc_count));
1525
+ rb_hash_aset(item, ID2SYM(id_free_count), SIZET2NUM(stats[i].free_count));
1526
+ rb_hash_aset(out, ID2SYM(rb_intern(se_alloc_category_name((se_alloc_category_t)i))), item);
1527
+ }
1528
+
1529
+ return out;
1530
+ }
1531
+
1532
+ static VALUE se_alloc_stats_reset_bang(VALUE self) {
1533
+ (void)self;
1534
+ se_alloc_stats_reset();
1535
+ return Qnil;
1536
+ }
1537
+ #endif
1538
+
2014
1539
  RUBY_FUNC_EXPORTED void Init_static_embeddings(void) {
2015
- select_f16_backend();
1540
+ se_select_f16_backend();
2016
1541
  binary_encoding = rb_ascii8bit_encoding();
2017
1542
  utf8_encoding = rb_utf8_encoding();
2018
1543
  id_join = rb_intern("join");
@@ -2028,6 +1553,9 @@ RUBY_FUNC_EXPORTED void Init_static_embeddings(void) {
2028
1553
  id_dim = rb_intern("dim");
2029
1554
  id_allow_unfrozen = rb_intern("allow_unfrozen");
2030
1555
 
1556
+ id_validate_encoding = rb_intern("validate_encoding");
1557
+ id_full = rb_intern("full");
1558
+ id_prefix = rb_intern("prefix");
2031
1559
  mStaticEmbeddings = rb_define_module("StaticEmbeddings");
2032
1560
  cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
2033
1561
 
@@ -2063,6 +1591,11 @@ RUBY_FUNC_EXPORTED void Init_static_embeddings(void) {
2063
1591
  rb_define_singleton_method(mStaticEmbeddings, "encode_f16", se_encode_f16, 1);
2064
1592
  rb_define_singleton_method(mStaticEmbeddings, "decode_f16", se_decode_f16, 1);
2065
1593
  rb_define_singleton_method(mStaticEmbeddings, "simd_backend", se_simd_backend, 0);
1594
+ #if SE_ENABLE_ALLOC_STATS
1595
+ rb_define_singleton_method(mStaticEmbeddings, "__alloc_stats__", se_alloc_stats_hash, 0);
1596
+ rb_define_singleton_method(mStaticEmbeddings, "__alloc_stats_reset__",
1597
+ se_alloc_stats_reset_bang, 0);
1598
+ #endif
2066
1599
 
2067
1600
  rb_define_const(mStaticEmbeddings, "FORMAT_VERSION", UINT2NUM(SE_FORMAT_VERSION));
2068
1601
  rb_define_const(mStaticEmbeddings, "TOKENIZER_BERT_WORDPIECE_V1",