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.
@@ -14,6 +14,8 @@
14
14
  #ifndef O_CLOEXEC
15
15
  #define O_CLOEXEC 0
16
16
  #endif
17
+ #else
18
+ #include <windows.h>
17
19
  #endif
18
20
 
19
21
  void se_error_clear(se_error_t *err) {
@@ -278,6 +280,68 @@ static se_status_t parse_trie(se_trie_t *trie, const uint8_t *base, struct secti
278
280
  return SE_OK;
279
281
  }
280
282
 
283
+ static int valid_unicode_codepoint(uint32_t cp) {
284
+ return cp <= 0x10FFFFu && !(cp >= 0xD800u && cp <= 0xDFFFu);
285
+ }
286
+
287
+ static se_status_t validate_norm_map(const se_map_entry_t *entries, uint32_t count,
288
+ const char *name, se_error_t *err) {
289
+ uint32_t prev = 0;
290
+
291
+ for (uint32_t i = 0; i < count; i++) {
292
+ const se_map_entry_t *entry = &entries[i];
293
+ if (entry->len > SE_ARRAY_LEN(entry->out)) {
294
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map entry %u has len %u", name, i,
295
+ entry->len);
296
+ return SE_ERR_INVALID_FORMAT;
297
+ }
298
+ if (!valid_unicode_codepoint(entry->cp)) {
299
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map entry %u has invalid codepoint", name,
300
+ i);
301
+ return SE_ERR_INVALID_FORMAT;
302
+ }
303
+ for (uint32_t k = 0; k < entry->len; k++) {
304
+ if (!valid_unicode_codepoint(entry->out[k])) {
305
+ se_error_set(err, SE_ERR_INVALID_FORMAT,
306
+ "%s map entry %u output %u is not a valid codepoint", name, i, k);
307
+ return SE_ERR_INVALID_FORMAT;
308
+ }
309
+ }
310
+ if (i && entry->cp <= prev) {
311
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map is not strictly sorted", name);
312
+ return SE_ERR_INVALID_FORMAT;
313
+ }
314
+ prev = entry->cp;
315
+ }
316
+
317
+ return SE_OK;
318
+ }
319
+
320
+ static se_status_t validate_norm_ranges(const se_range_t *ranges, uint32_t count, const char *name,
321
+ se_error_t *err) {
322
+ uint32_t prev_hi = 0;
323
+
324
+ for (uint32_t i = 0; i < count; i++) {
325
+ const se_range_t *range = &ranges[i];
326
+ if (!valid_unicode_codepoint(range->hi)) {
327
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s range %u exceeds the Unicode range", name,
328
+ i);
329
+ return SE_ERR_INVALID_FORMAT;
330
+ }
331
+ if (range->lo > range->hi) {
332
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s range %u is inverted", name, i);
333
+ return SE_ERR_INVALID_FORMAT;
334
+ }
335
+ if (i && range->lo <= prev_hi) {
336
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "%s ranges overlap or are unsorted", name);
337
+ return SE_ERR_INVALID_FORMAT;
338
+ }
339
+ prev_hi = range->hi;
340
+ }
341
+
342
+ return SE_OK;
343
+ }
344
+
281
345
  static se_status_t parse_norm_tables(se_model_t *model, const uint8_t *base, struct section sec,
282
346
  se_error_t *err) {
283
347
  memset(&model->norm, 0, sizeof(model->norm));
@@ -338,66 +402,112 @@ static se_status_t parse_norm_tables(se_model_t *model, const uint8_t *base, str
338
402
  model->norm.whitespace = (const se_range_t *)cursor;
339
403
  model->norm.whitespace_count = n_ws;
340
404
 
341
- const se_map_entry_t *maps[] = {model->norm.lower, model->norm.nfd};
342
- const uint32_t map_counts[] = {model->norm.lower_count, model->norm.nfd_count};
343
- const char *map_names[] = {"lower", "nfd"};
344
- for (size_t table = 0; table < 2; table++) {
345
- uint32_t prev = 0;
346
- for (uint32_t i = 0; i < map_counts[table]; i++) {
347
- const se_map_entry_t *entry = &maps[table][i];
348
- if (entry->len > 4) {
349
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map entry %u has len %u",
350
- map_names[table], i, entry->len);
351
- return SE_ERR_INVALID_FORMAT;
352
- }
353
- if (entry->cp > 0x10FFFFu || (entry->cp >= 0xD800u && entry->cp <= 0xDFFFu)) {
354
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map entry %u has invalid codepoint",
355
- map_names[table], i);
356
- return SE_ERR_INVALID_FORMAT;
357
- }
358
- for (uint32_t k = 0; k < entry->len; k++) {
359
- uint32_t out_cp = entry->out[k];
360
- if (out_cp > 0x10FFFFu || (out_cp >= 0xD800u && out_cp <= 0xDFFFu)) {
361
- se_error_set(err, SE_ERR_INVALID_FORMAT,
362
- "%s map entry %u output %u is not a valid codepoint",
363
- map_names[table], i, k);
364
- return SE_ERR_INVALID_FORMAT;
365
- }
366
- }
367
- if (i && entry->cp <= prev) {
368
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s map is not strictly sorted",
369
- map_names[table]);
370
- return SE_ERR_INVALID_FORMAT;
371
- }
372
- prev = entry->cp;
405
+ se_status_t rc = validate_norm_map(model->norm.lower, model->norm.lower_count, "lower", err);
406
+ if (rc != SE_OK)
407
+ return rc;
408
+ rc = validate_norm_map(model->norm.nfd, model->norm.nfd_count, "nfd", err);
409
+ if (rc != SE_OK)
410
+ return rc;
411
+ rc = validate_norm_ranges(model->norm.mn, model->norm.mn_count, "mn", err);
412
+ if (rc != SE_OK)
413
+ return rc;
414
+ rc = validate_norm_ranges(model->norm.punct, model->norm.punct_count, "punct", err);
415
+ if (rc != SE_OK)
416
+ return rc;
417
+ rc = validate_norm_ranges(model->norm.control, model->norm.control_count, "control", err);
418
+ if (rc != SE_OK)
419
+ return rc;
420
+ return validate_norm_ranges(model->norm.whitespace, model->norm.whitespace_count, "whitespace",
421
+ err);
422
+ }
423
+
424
+ static se_status_t validate_vocab_hash(const se_model_t *model, struct section vocab_strings,
425
+ se_error_t *err) {
426
+ const se_meta_t *m = &model->meta;
427
+ size_t bitset_size = ((size_t)m->vocab_size + 7u) / 8u;
428
+ size_t seen_bytes = 0;
429
+ uint32_t filled = 0;
430
+ se_status_t rc = SE_OK;
431
+
432
+ if (!se_alloc_bytes(bitset_size, 1u, &seen_bytes)) {
433
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "vocabulary size is too large");
434
+ return SE_ERR_INVALID_FORMAT;
435
+ }
436
+
437
+ uint8_t *seen = (uint8_t *)se_calloc(SE_ALLOC_FORMAT_VALIDATE, 1, seen_bytes);
438
+ if (!seen) {
439
+ se_error_set(err, SE_ERR_OOM, "out of memory while validating vocabulary hash");
440
+ return SE_ERR_OOM;
441
+ }
442
+
443
+ for (uint32_t i = 0; i < m->hash_table_size; i++) {
444
+ const se_vocab_slot_t *slot = &model->vocab_hash[i];
445
+ if (slot->token_id == SE_SLOT_EMPTY)
446
+ continue;
447
+ if (slot->token_id >= m->vocab_size) {
448
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u has token id out of range", i);
449
+ rc = SE_ERR_INVALID_FORMAT;
450
+ goto done;
373
451
  }
452
+ if ((uint64_t)slot->str_off + slot->str_len > vocab_strings.size) {
453
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u points outside the string blob", i);
454
+ rc = SE_ERR_INVALID_FORMAT;
455
+ goto done;
456
+ }
457
+
458
+ uint8_t mask = (uint8_t)(1u << (slot->token_id & 7u));
459
+ uint8_t *byte = &seen[slot->token_id >> 3];
460
+ if (*byte & mask) {
461
+ se_error_set(err, SE_ERR_INVALID_FORMAT,
462
+ "token id %u appears more than once in hash table", slot->token_id);
463
+ rc = SE_ERR_INVALID_FORMAT;
464
+ goto done;
465
+ }
466
+
467
+ *byte |= mask;
468
+ filled++;
374
469
  }
375
470
 
376
- const se_range_t *ranges[] = {model->norm.mn, model->norm.punct, model->norm.control,
377
- model->norm.whitespace};
378
- const uint32_t range_counts[] = {model->norm.mn_count, model->norm.punct_count,
379
- model->norm.control_count, model->norm.whitespace_count};
380
- const char *range_names[] = {"mn", "punct", "control", "whitespace"};
381
- for (size_t table = 0; table < 4; table++) {
382
- uint32_t prev_hi = 0;
383
- for (uint32_t i = 0; i < range_counts[table]; i++) {
384
- const se_range_t *range = &ranges[table][i];
385
- if (range->hi > 0x10FFFFu) {
386
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s range %u exceeds the Unicode range",
387
- range_names[table], i);
388
- return SE_ERR_INVALID_FORMAT;
389
- }
390
- if (range->lo > range->hi) {
391
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s range %u is inverted",
392
- range_names[table], i);
393
- return SE_ERR_INVALID_FORMAT;
394
- }
395
- if (i && range->lo <= prev_hi) {
396
- se_error_set(err, SE_ERR_INVALID_FORMAT, "%s ranges overlap or are unsorted",
397
- range_names[table]);
471
+ if (filled != m->vocab_size) {
472
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "hash table has %u filled slots, expected %u",
473
+ filled, m->vocab_size);
474
+ rc = SE_ERR_INVALID_FORMAT;
475
+ goto done;
476
+ }
477
+
478
+ uint32_t unk_lookup = 0;
479
+ if (!se_vocab_lookup(model, (const uint8_t *)"[UNK]", 5, &unk_lookup) ||
480
+ unk_lookup != m->unk_id) {
481
+ se_error_set(err, SE_ERR_INVALID_FORMAT,
482
+ "[UNK] is not reachable through the vocabulary hash");
483
+ rc = SE_ERR_INVALID_FORMAT;
484
+ }
485
+
486
+ done:
487
+ se_free(seen);
488
+ return rc;
489
+ }
490
+
491
+ static se_status_t validate_hash_probe_limits(const se_model_t *model, se_error_t *err) {
492
+ const se_meta_t *m = &model->meta;
493
+ if (m->max_probe == 0)
494
+ return SE_OK;
495
+
496
+ uint32_t mask = m->hash_table_size - 1u;
497
+ for (uint32_t i = 0; i < m->hash_table_size; i++) {
498
+ const se_vocab_slot_t *slot = &model->vocab_hash[i];
499
+ if (slot->token_id == SE_SLOT_EMPTY)
500
+ continue;
501
+
502
+ uint32_t pos = slot->hash & mask;
503
+ uint32_t probes = 1;
504
+ while (pos != i) {
505
+ probes++;
506
+ pos = (pos + 1u) & mask;
507
+ if (probes > m->max_probe) {
508
+ se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u exceeds recorded max probe", i);
398
509
  return SE_ERR_INVALID_FORMAT;
399
510
  }
400
- prev_hi = range->hi;
401
511
  }
402
512
  }
403
513
 
@@ -408,6 +518,12 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
408
518
  const uint8_t *base = (const uint8_t *)model->map_base;
409
519
  const uint64_t file_size = (uint64_t)model->map_size;
410
520
 
521
+ if (!se_host_is_little_endian()) {
522
+ se_error_set(err, SE_ERR_UNSUPPORTED_VERSION,
523
+ "static_embeddings .semb files require a little-endian host");
524
+ return SE_ERR_UNSUPPORTED_VERSION;
525
+ }
526
+
411
527
  if (file_size < SE_HEADER_SIZE) {
412
528
  se_error_set(err, SE_ERR_INVALID_FORMAT, "file shorter than header");
413
529
  return SE_ERR_INVALID_FORMAT;
@@ -553,8 +669,7 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
553
669
 
554
670
  const struct section sections[] = {vocab_strings, vocab_hash, embeddings, norm,
555
671
  provenance, root_trie, cont_trie};
556
- se_status_t overlap_status =
557
- validate_no_overlaps(sections, sizeof(sections) / sizeof(sections[0]), err);
672
+ se_status_t overlap_status = validate_no_overlaps(sections, SE_ARRAY_LEN(sections), err);
558
673
  if (overlap_status != SE_OK)
559
674
  return overlap_status;
560
675
 
@@ -585,74 +700,13 @@ static se_status_t validate(se_model_t *model, se_error_t *err) {
585
700
  model->provenance = provenance.size ? (const char *)(base + provenance.off) : NULL;
586
701
  model->provenance_size = (size_t)provenance.size;
587
702
 
588
- size_t bitset_size = ((size_t)m->vocab_size + 7u) / 8u;
589
- uint8_t *seen = (uint8_t *)calloc(bitset_size ? bitset_size : 1u, 1u);
590
- if (!seen) {
591
- se_error_set(err, SE_ERR_OOM, "out of memory while validating vocabulary hash");
592
- return SE_ERR_OOM;
593
- }
594
-
595
- uint32_t filled = 0;
596
- for (uint32_t i = 0; i < m->hash_table_size; i++) {
597
- const se_vocab_slot_t *slot = &model->vocab_hash[i];
598
- if (slot->token_id == SE_SLOT_EMPTY)
599
- continue;
600
- if (slot->token_id >= m->vocab_size) {
601
- free(seen);
602
- se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u has token id out of range", i);
603
- return SE_ERR_INVALID_FORMAT;
604
- }
605
- if ((uint64_t)slot->str_off + slot->str_len > vocab_strings.size) {
606
- free(seen);
607
- se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u points outside the string blob", i);
608
- return SE_ERR_INVALID_FORMAT;
609
- }
610
- uint8_t mask = (uint8_t)(1u << (slot->token_id & 7u));
611
- uint8_t *byte = &seen[slot->token_id >> 3];
612
- if (*byte & mask) {
613
- free(seen);
614
- se_error_set(err, SE_ERR_INVALID_FORMAT,
615
- "token id %u appears more than once in hash table", slot->token_id);
616
- return SE_ERR_INVALID_FORMAT;
617
- }
618
- *byte |= mask;
619
- filled++;
620
- }
621
- free(seen);
622
-
623
- if (filled != m->vocab_size) {
624
- se_error_set(err, SE_ERR_INVALID_FORMAT, "hash table has %u filled slots, expected %u",
625
- filled, m->vocab_size);
626
- return SE_ERR_INVALID_FORMAT;
627
- }
703
+ se_status_t hash_status = validate_vocab_hash(model, vocab_strings, err);
704
+ if (hash_status != SE_OK)
705
+ return hash_status;
628
706
 
629
- uint32_t unk_lookup = 0;
630
- if (!se_vocab_lookup(model, (const uint8_t *)"[UNK]", 5, &unk_lookup) ||
631
- unk_lookup != m->unk_id) {
632
- se_error_set(err, SE_ERR_INVALID_FORMAT,
633
- "[UNK] is not reachable through the vocabulary hash");
634
- return SE_ERR_INVALID_FORMAT;
635
- }
636
-
637
- if (m->max_probe > 0) {
638
- uint32_t mask = m->hash_table_size - 1u;
639
- for (uint32_t i = 0; i < m->hash_table_size; i++) {
640
- const se_vocab_slot_t *slot = &model->vocab_hash[i];
641
- if (slot->token_id == SE_SLOT_EMPTY)
642
- continue;
643
- uint32_t pos = slot->hash & mask;
644
- uint32_t probes = 1;
645
- while (pos != i) {
646
- probes++;
647
- pos = (pos + 1u) & mask;
648
- if (probes > m->max_probe) {
649
- se_error_set(err, SE_ERR_INVALID_FORMAT, "slot %u exceeds recorded max probe",
650
- i);
651
- return SE_ERR_INVALID_FORMAT;
652
- }
653
- }
654
- }
655
- }
707
+ se_status_t probe_status = validate_hash_probe_limits(model, err);
708
+ if (probe_status != SE_OK)
709
+ return probe_status;
656
710
 
657
711
  se_status_t trie_status =
658
712
  parse_trie(&model->root_trie, base, root_trie, m->vocab_size, "root", err);
@@ -701,35 +755,65 @@ se_status_t se_model_open(se_model_t *model, const char *path, se_error_t *err)
701
755
  model->map_size = (size_t)st.st_size;
702
756
  model->mapped = 1;
703
757
  #else
704
- FILE *f = fopen(path, "rb");
705
- if (!f) {
758
+ HANDLE file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
759
+ FILE_ATTRIBUTE_NORMAL, NULL);
760
+ if (file == INVALID_HANDLE_VALUE) {
706
761
  se_error_set(err, SE_ERR_IO, "cannot open %s", path);
707
762
  return SE_ERR_IO;
708
763
  }
709
- fseek(f, 0, SEEK_END);
710
- long size = ftell(f);
711
- fseek(f, 0, SEEK_SET);
712
- if (size <= 0) {
713
- fclose(f);
714
- se_error_set(err, SE_ERR_IO, "empty file %s", path);
764
+
765
+ LARGE_INTEGER file_size;
766
+ if (!GetFileSizeEx(file, &file_size) || file_size.QuadPart <= 0) {
767
+ CloseHandle(file);
768
+ se_error_set(err, SE_ERR_IO, "cannot stat %s", path);
715
769
  return SE_ERR_IO;
716
770
  }
717
- void *buf = malloc((size_t)size);
718
- if (!buf) {
719
- fclose(f);
720
- se_error_set(err, SE_ERR_OOM, "out of memory");
721
- return SE_ERR_OOM;
722
- }
723
- if (fread(buf, 1, (size_t)size, f) != (size_t)size) {
724
- free(buf);
725
- fclose(f);
726
- se_error_set(err, SE_ERR_IO, "short read on %s", path);
771
+
772
+ if ((uint64_t)file_size.QuadPart > (uint64_t)SIZE_MAX) {
773
+ CloseHandle(file);
774
+ se_error_set(err, SE_ERR_IO, "file is too large to map safely");
727
775
  return SE_ERR_IO;
728
776
  }
729
- fclose(f);
730
- model->map_base = buf;
731
- model->map_size = (size_t)size;
732
- model->mapped = 0;
777
+
778
+ void *view = NULL;
779
+ HANDLE mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL);
780
+ if (mapping) {
781
+ view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
782
+ CloseHandle(mapping);
783
+ }
784
+
785
+ if (view) {
786
+ CloseHandle(file);
787
+ model->map_base = view;
788
+ model->map_size = (size_t)file_size.QuadPart;
789
+ model->mapped = 1;
790
+ } else {
791
+ size_t size = (size_t)file_size.QuadPart;
792
+ void *buf = se_malloc(SE_ALLOC_MODEL_FILE, size);
793
+ if (!buf) {
794
+ CloseHandle(file);
795
+ se_error_set(err, SE_ERR_OOM, "out of memory");
796
+ return SE_ERR_OOM;
797
+ }
798
+
799
+ size_t got = 0;
800
+ while (got < size) {
801
+ DWORD chunk = (DWORD)((size - got) > 0x10000000u ? 0x10000000u : (size - got));
802
+ DWORD read = 0;
803
+ if (!ReadFile(file, (uint8_t *)buf + got, chunk, &read, NULL) || read == 0) {
804
+ se_free(buf);
805
+ CloseHandle(file);
806
+ se_error_set(err, SE_ERR_IO, "short read on %s", path);
807
+ return SE_ERR_IO;
808
+ }
809
+ got += read;
810
+ }
811
+
812
+ CloseHandle(file);
813
+ model->map_base = buf;
814
+ model->map_size = size;
815
+ model->mapped = 0;
816
+ }
733
817
  #endif
734
818
 
735
819
  se_status_t rc = validate(model, err);
@@ -747,9 +831,12 @@ void se_model_close(se_model_t *model) {
747
831
  if (model->mapped)
748
832
  munmap(model->map_base, model->map_size);
749
833
  else
750
- free(model->map_base);
834
+ se_free(model->map_base);
751
835
  #else
752
- free(model->map_base);
836
+ if (model->mapped)
837
+ UnmapViewOfFile(model->map_base);
838
+ else
839
+ se_free(model->map_base);
753
840
  #endif
754
841
  memset(model, 0, sizeof(*model));
755
842
  }
@@ -4,6 +4,7 @@
4
4
  #include <stddef.h>
5
5
  #include <stdint.h>
6
6
  #include <signal.h>
7
+ #include <limits.h>
7
8
 
8
9
  #define SE_MAGIC "SEMBv1\0\0"
9
10
  #define SE_MAGIC_LEN 8
@@ -26,6 +27,22 @@
26
27
  #define SE_EMPTY_RAISE 1u
27
28
 
28
29
  #define SE_SLOT_EMPTY 0xFFFFFFFFu
30
+ #define SE_SIZE_MAX ((size_t)-1)
31
+
32
+ #ifndef SE_ENABLE_ALLOC_STATS
33
+ #define SE_ENABLE_ALLOC_STATS 0
34
+ #endif
35
+
36
+ #define SE_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
37
+
38
+ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
39
+ #define SE_STATIC_ASSERT(expr, name) _Static_assert((expr), #name)
40
+ #else
41
+ #define SE_STATIC_ASSERT_JOIN_INNER(a, b) a##b
42
+ #define SE_STATIC_ASSERT_JOIN(a, b) SE_STATIC_ASSERT_JOIN_INNER(a, b)
43
+ #define SE_STATIC_ASSERT(expr, name) \
44
+ typedef char SE_STATIC_ASSERT_JOIN(se_static_assertion_, name)[(expr) ? 1 : -1]
45
+ #endif
29
46
 
30
47
  enum {
31
48
  SE_OFF_MAGIC = 0,
@@ -199,6 +216,110 @@ typedef struct {
199
216
  char message[256];
200
217
  } se_error_t;
201
218
 
219
+ typedef enum { SE_VECTOR_FORMAT_F32 = 1, SE_VECTOR_FORMAT_F16 = 2 } se_vector_format_t;
220
+
221
+ typedef enum {
222
+ SE_F16_BACKEND_LUT = 0,
223
+ SE_F16_BACKEND_NEON_FP16 = 1,
224
+ SE_F16_BACKEND_F16C = 2
225
+ } se_f16_backend_t;
226
+
227
+ typedef enum {
228
+ SE_ALLOC_UNKNOWN = 0,
229
+ SE_ALLOC_SCRATCH,
230
+ SE_ALLOC_BATCH_INPUT,
231
+ SE_ALLOC_BATCH_OUTPUT,
232
+ SE_ALLOC_BATCH_STATS,
233
+ SE_ALLOC_BATCH_INDEX,
234
+ SE_ALLOC_TOKEN_IDS,
235
+ SE_ALLOC_TOPK_QUERY,
236
+ SE_ALLOC_TOPK_BEST,
237
+ SE_ALLOC_TOPK_MATRIX_COPY,
238
+ SE_ALLOC_FORMAT_VALIDATE,
239
+ SE_ALLOC_MODEL_FILE,
240
+ SE_ALLOC_CATEGORY_COUNT
241
+ } se_alloc_category_t;
242
+
243
+ typedef struct {
244
+ size_t current_bytes;
245
+ size_t peak_bytes;
246
+ size_t total_allocated_bytes;
247
+ size_t total_freed_bytes;
248
+ size_t alloc_count;
249
+ size_t realloc_count;
250
+ size_t free_count;
251
+ } se_alloc_stats_t;
252
+
253
+ typedef struct {
254
+ const float *q;
255
+ const void *m;
256
+ se_vector_format_t format;
257
+ size_t dim;
258
+ size_t rows;
259
+ long k;
260
+ size_t *best_idx;
261
+ float *best_score;
262
+ int cosine;
263
+ float inv_query_norm;
264
+ volatile sig_atomic_t cancelled;
265
+ } se_topk_job_t;
266
+
267
+ SE_STATIC_ASSERT(sizeof(se_vocab_slot_t) == 16, vocab_slot_size);
268
+ SE_STATIC_ASSERT(sizeof(se_trie_node_t) == 16, trie_node_size);
269
+ SE_STATIC_ASSERT(sizeof(se_trie_edge_t) == 8, trie_edge_size);
270
+ SE_STATIC_ASSERT(sizeof(se_map_entry_t) == 24, map_entry_size);
271
+ SE_STATIC_ASSERT(sizeof(se_range_t) == 8, range_size);
272
+
273
+ static inline int se_checked_add_size(size_t a, size_t b, size_t *out) {
274
+ if (a > SE_SIZE_MAX - b)
275
+ return 0;
276
+ *out = a + b;
277
+ return 1;
278
+ }
279
+
280
+ static inline int se_checked_mul_size(size_t a, size_t b, size_t *out) {
281
+ if (a != 0 && b > SE_SIZE_MAX / a)
282
+ return 0;
283
+ *out = a * b;
284
+ return 1;
285
+ }
286
+
287
+ static inline int se_array_bytes(size_t count, size_t elem_size, size_t *out) {
288
+ return se_checked_mul_size(count, elem_size, out);
289
+ }
290
+
291
+ static inline size_t se_alloc_count(size_t count) {
292
+ return count ? count : 1;
293
+ }
294
+
295
+ static inline int se_alloc_bytes(size_t count, size_t elem_size, size_t *out) {
296
+ return se_checked_mul_size(se_alloc_count(count), elem_size, out);
297
+ }
298
+
299
+ static inline int se_next_capacity(size_t current, size_t need, size_t min_capacity, size_t *out) {
300
+ size_t next = current ? current : min_capacity;
301
+
302
+ if (next == 0)
303
+ next = 1;
304
+ while (next < need) {
305
+ if (next > SE_SIZE_MAX / 2)
306
+ return 0;
307
+ next *= 2;
308
+ }
309
+
310
+ *out = next;
311
+ return 1;
312
+ }
313
+
314
+ static inline int se_size_fits_long(size_t n) {
315
+ return n <= (size_t)LONG_MAX;
316
+ }
317
+
318
+ static inline int se_host_is_little_endian(void) {
319
+ const uint16_t one = 1;
320
+ return *((const uint8_t *)(const void *)&one) == 1;
321
+ }
322
+
202
323
  #if defined(__GNUC__) || defined(__clang__)
203
324
  #define SE_PRINTF_FORMAT(fmt_index, first_arg) __attribute__((format(printf, fmt_index, first_arg)))
204
325
  #else
@@ -208,6 +329,28 @@ typedef struct {
208
329
  void se_error_clear(se_error_t *err);
209
330
  void se_error_set(se_error_t *err, se_status_t status, const char *fmt, ...) SE_PRINTF_FORMAT(3, 4);
210
331
 
332
+ const char *se_alloc_category_name(se_alloc_category_t category);
333
+ void se_alloc_stats_reset(void);
334
+ void se_alloc_stats_snapshot(se_alloc_stats_t out[SE_ALLOC_CATEGORY_COUNT]);
335
+
336
+ #if SE_ENABLE_ALLOC_STATS
337
+ void *se_alloc_stats_malloc(se_alloc_category_t category, size_t bytes);
338
+ void *se_alloc_stats_calloc(se_alloc_category_t category, size_t count, size_t elem_size);
339
+ void *se_alloc_stats_realloc(se_alloc_category_t category, void *ptr, size_t bytes);
340
+ void se_alloc_stats_free(void *ptr);
341
+
342
+ #define se_malloc(category, bytes) se_alloc_stats_malloc((category), (bytes))
343
+ #define se_calloc(category, count, elem_size) \
344
+ se_alloc_stats_calloc((category), (count), (elem_size))
345
+ #define se_realloc(category, ptr, bytes) se_alloc_stats_realloc((category), (ptr), (bytes))
346
+ #define se_free(ptr) se_alloc_stats_free((ptr))
347
+ #else
348
+ #define se_malloc(category, bytes) malloc((bytes))
349
+ #define se_calloc(category, count, elem_size) calloc((count), (elem_size))
350
+ #define se_realloc(category, ptr, bytes) realloc((ptr), (bytes))
351
+ #define se_free(ptr) free((ptr))
352
+ #endif
353
+
211
354
  se_status_t se_model_open(se_model_t *model, const char *path, se_error_t *err);
212
355
  void se_model_close(se_model_t *model);
213
356
  size_t se_model_memsize(const se_model_t *model);
@@ -263,4 +406,20 @@ se_status_t se_embed_ids(const se_model_t *model, se_scratch_t *scratch, const u
263
406
  void se_l2_normalize(float *vec, uint32_t dim);
264
407
  size_t se_model_warmup(const se_model_t *model);
265
408
 
409
+ size_t se_vector_format_element_bytes(se_vector_format_t format);
410
+ uint16_t se_float_to_f16_bits(float value);
411
+ float se_f16_bits_to_float(uint16_t half);
412
+ void se_write_f16le(uint8_t *dst, float value);
413
+ float se_read_f16le(const uint8_t *src);
414
+ void se_encode_f16_from_floats(uint8_t *dst, const float *src, size_t count);
415
+ void se_decode_f16_to_floats(float *dst, const uint8_t *src, size_t count);
416
+ void se_select_f16_backend(void);
417
+ se_f16_backend_t se_current_f16_backend(void);
418
+
419
+ float se_dot_product_f32(const float *q, const float *row, size_t dim);
420
+ float se_dot_and_row_sq_f32(const float *q, const float *row, size_t dim, float *row_sq_out);
421
+ float se_dot_product_f16(const float *q, const uint8_t *row, size_t dim);
422
+ float se_dot_and_row_sq_f16(const float *q, const uint8_t *row, size_t dim, float *row_sq_out);
423
+ void *se_topk_execute(void *arg);
424
+
266
425
  #endif