static_embeddings 0.1.1
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 +7 -0
- data/CHANGELOG.md +136 -0
- data/LICENSE.txt +21 -0
- data/README.md +439 -0
- data/docs/ARCHITECTURE.md +278 -0
- data/docs/MODEL_AUDIT.md +61 -0
- data/docs/PERFORMANCE.md +60 -0
- data/exe/static_embeddings +6 -0
- data/ext/static_embeddings/extconf.rb +44 -0
- data/ext/static_embeddings/se_embed.c +192 -0
- data/ext/static_embeddings/se_format.c +762 -0
- data/ext/static_embeddings/se_internal.h +266 -0
- data/ext/static_embeddings/se_tokenizer.c +506 -0
- data/ext/static_embeddings/se_unicode.c +120 -0
- data/ext/static_embeddings/static_embeddings.c +2070 -0
- data/lib/static_embeddings/cli.rb +178 -0
- data/lib/static_embeddings/converter.rb +284 -0
- data/lib/static_embeddings/errors.rb +6 -0
- data/lib/static_embeddings/format.rb +289 -0
- data/lib/static_embeddings/model.rb +48 -0
- data/lib/static_embeddings/paths.rb +29 -0
- data/lib/static_embeddings/reference.rb +191 -0
- data/lib/static_embeddings/safetensors.rb +87 -0
- data/lib/static_embeddings/unicode_tables.rb +127 -0
- data/lib/static_embeddings/version.rb +3 -0
- data/lib/static_embeddings.rb +118 -0
- data/static_embeddings.gemspec +45 -0
- data/tools/benchmark.rb +38 -0
- data/tools/build_demo_model.rb +16 -0
- data/tools/check_model2vec_parity.rb +107 -0
- data/tools/make_fixture_model.rb +165 -0
- metadata +134 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
#include "se_internal.h"
|
|
2
|
+
|
|
3
|
+
#include <stdarg.h>
|
|
4
|
+
#include <stdio.h>
|
|
5
|
+
#include <limits.h>
|
|
6
|
+
#include <stdlib.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
|
|
9
|
+
#ifndef _WIN32
|
|
10
|
+
#include <fcntl.h>
|
|
11
|
+
#include <sys/mman.h>
|
|
12
|
+
#include <sys/stat.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
#ifndef O_CLOEXEC
|
|
15
|
+
#define O_CLOEXEC 0
|
|
16
|
+
#endif
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
void se_error_clear(se_error_t *err) {
|
|
20
|
+
if (!err)
|
|
21
|
+
return;
|
|
22
|
+
err->status = SE_OK;
|
|
23
|
+
err->message[0] = '\0';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void se_error_set(se_error_t *err, se_status_t status, const char *fmt, ...) {
|
|
27
|
+
if (!err)
|
|
28
|
+
return;
|
|
29
|
+
err->status = status;
|
|
30
|
+
va_list ap;
|
|
31
|
+
va_start(ap, fmt);
|
|
32
|
+
vsnprintf(err->message, sizeof(err->message), fmt, ap);
|
|
33
|
+
va_end(ap);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static uint32_t read_u32(const uint8_t *base, size_t off) {
|
|
37
|
+
return (uint32_t)base[off] | ((uint32_t)base[off + 1] << 8) | ((uint32_t)base[off + 2] << 16) |
|
|
38
|
+
((uint32_t)base[off + 3] << 24);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static uint64_t read_u64(const uint8_t *base, size_t off) {
|
|
42
|
+
return (uint64_t)read_u32(base, off) | ((uint64_t)read_u32(base, off + 4) << 32);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
uint32_t se_hash_bytes(const uint8_t *data, size_t len, uint32_t seed) {
|
|
46
|
+
uint32_t h = seed;
|
|
47
|
+
for (size_t i = 0; i < len; i++) {
|
|
48
|
+
h ^= (uint32_t)data[i];
|
|
49
|
+
h *= 16777619u;
|
|
50
|
+
}
|
|
51
|
+
return h;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static uint32_t hash_bytes_continue(uint32_t h, const uint8_t *data, size_t len) {
|
|
55
|
+
for (size_t i = 0; i < len; i++) {
|
|
56
|
+
h ^= (uint32_t)data[i];
|
|
57
|
+
h *= 16777619u;
|
|
58
|
+
}
|
|
59
|
+
return h;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
int se_vocab_lookup_piece(const se_model_t *model, const uint8_t *prefix, size_t prefix_len,
|
|
63
|
+
const uint8_t *bytes, size_t len, uint32_t *id_out) {
|
|
64
|
+
if (prefix_len > UINT32_MAX || len > UINT32_MAX || prefix_len > UINT32_MAX - len)
|
|
65
|
+
return 0;
|
|
66
|
+
|
|
67
|
+
uint32_t h = model->meta.hash_seed;
|
|
68
|
+
h = hash_bytes_continue(h, prefix, prefix_len);
|
|
69
|
+
h = hash_bytes_continue(h, bytes, len);
|
|
70
|
+
|
|
71
|
+
const uint32_t mask = model->meta.hash_table_size - 1u;
|
|
72
|
+
uint32_t pos = h & mask;
|
|
73
|
+
uint32_t total_len = (uint32_t)(prefix_len + len);
|
|
74
|
+
|
|
75
|
+
for (uint32_t probe = 0; probe <= mask; probe++) {
|
|
76
|
+
const se_vocab_slot_t *slot = &model->vocab_hash[pos];
|
|
77
|
+
if (slot->token_id == SE_SLOT_EMPTY)
|
|
78
|
+
return 0;
|
|
79
|
+
if (slot->hash == h && slot->str_len == total_len) {
|
|
80
|
+
const char *token = model->vocab_strings + slot->str_off;
|
|
81
|
+
if ((prefix_len == 0 || memcmp(token, prefix, prefix_len) == 0) &&
|
|
82
|
+
(len == 0 || memcmp(token + prefix_len, bytes, len) == 0)) {
|
|
83
|
+
*id_out = slot->token_id;
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
pos = (pos + 1u) & mask;
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
int se_vocab_lookup(const se_model_t *model, const uint8_t *bytes, size_t len, uint32_t *id_out) {
|
|
93
|
+
return se_vocab_lookup_piece(model, NULL, 0, bytes, len, id_out);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static inline uint32_t trie_find_child(const se_trie_t *trie, const se_trie_node_t *node,
|
|
97
|
+
uint32_t byte) {
|
|
98
|
+
uint32_t start = node->edge_start;
|
|
99
|
+
uint32_t count = node->edge_count;
|
|
100
|
+
|
|
101
|
+
if (count == 0)
|
|
102
|
+
return SE_SLOT_EMPTY;
|
|
103
|
+
|
|
104
|
+
if (count <= 8) {
|
|
105
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
106
|
+
const se_trie_edge_t *edge = &trie->edges[start + i];
|
|
107
|
+
if (edge->byte == byte)
|
|
108
|
+
return edge->child;
|
|
109
|
+
if (edge->byte > byte)
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
return SE_SLOT_EMPTY;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
uint32_t lo = start;
|
|
116
|
+
uint32_t hi = start + count;
|
|
117
|
+
while (lo < hi) {
|
|
118
|
+
uint32_t mid = lo + ((hi - lo) >> 1);
|
|
119
|
+
const se_trie_edge_t *edge = &trie->edges[mid];
|
|
120
|
+
if (edge->byte < byte)
|
|
121
|
+
lo = mid + 1u;
|
|
122
|
+
else
|
|
123
|
+
hi = mid;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (lo < start + count && trie->edges[lo].byte == byte)
|
|
127
|
+
return trie->edges[lo].child;
|
|
128
|
+
return SE_SLOT_EMPTY;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
int se_trie_longest_match(const se_trie_t *trie, const uint8_t *bytes, size_t len, uint32_t *id_out,
|
|
132
|
+
size_t *matched_len_out) {
|
|
133
|
+
if (!trie || !trie->nodes || trie->node_count == 0)
|
|
134
|
+
return 0;
|
|
135
|
+
|
|
136
|
+
uint32_t node_index = 0;
|
|
137
|
+
uint32_t best_id = SE_SLOT_EMPTY;
|
|
138
|
+
size_t best_len = 0;
|
|
139
|
+
|
|
140
|
+
for (size_t i = 0; i < len; i++) {
|
|
141
|
+
const se_trie_node_t *node = &trie->nodes[node_index];
|
|
142
|
+
uint32_t next = trie_find_child(trie, node, (uint32_t)bytes[i]);
|
|
143
|
+
if (next == SE_SLOT_EMPTY)
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
node_index = next;
|
|
147
|
+
uint32_t token_id = trie->nodes[node_index].token_id;
|
|
148
|
+
if (token_id != SE_SLOT_EMPTY) {
|
|
149
|
+
best_id = token_id;
|
|
150
|
+
best_len = i + 1u;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (best_id == SE_SLOT_EMPTY)
|
|
155
|
+
return 0;
|
|
156
|
+
|
|
157
|
+
*id_out = best_id;
|
|
158
|
+
*matched_len_out = best_len;
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
struct section {
|
|
163
|
+
uint64_t off;
|
|
164
|
+
uint64_t size;
|
|
165
|
+
const char *name;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
static int section_present(struct section s) {
|
|
169
|
+
return s.off != 0 || s.size != 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static uint64_t section_end(struct section s) {
|
|
173
|
+
return s.off + s.size;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static int section_ok(struct section s, uint64_t file_size, size_t elem_size) {
|
|
177
|
+
if (!section_present(s))
|
|
178
|
+
return 1;
|
|
179
|
+
if (s.off < SE_HEADER_SIZE)
|
|
180
|
+
return 0;
|
|
181
|
+
if (s.off % SE_ALIGNMENT != 0)
|
|
182
|
+
return 0;
|
|
183
|
+
if (s.size > file_size)
|
|
184
|
+
return 0;
|
|
185
|
+
if (s.off > file_size - s.size)
|
|
186
|
+
return 0;
|
|
187
|
+
if (elem_size && (s.size % elem_size) != 0)
|
|
188
|
+
return 0;
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static struct section read_section(const uint8_t *base, size_t off, const char *name) {
|
|
193
|
+
struct section s;
|
|
194
|
+
s.off = read_u64(base, off);
|
|
195
|
+
s.size = read_u64(base, off + 8);
|
|
196
|
+
s.name = name;
|
|
197
|
+
return s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static int sections_overlap(struct section a, struct section b) {
|
|
201
|
+
if (!section_present(a) || !section_present(b))
|
|
202
|
+
return 0;
|
|
203
|
+
return a.off < section_end(b) && b.off < section_end(a);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static se_status_t validate_no_overlaps(const struct section *sections, size_t count,
|
|
207
|
+
se_error_t *err) {
|
|
208
|
+
for (size_t i = 0; i < count; i++) {
|
|
209
|
+
for (size_t j = i + 1; j < count; j++) {
|
|
210
|
+
if (sections_overlap(sections[i], sections[j])) {
|
|
211
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "sections %s and %s overlap",
|
|
212
|
+
sections[i].name, sections[j].name);
|
|
213
|
+
return SE_ERR_INVALID_FORMAT;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return SE_OK;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static se_status_t parse_trie(se_trie_t *trie, const uint8_t *base, struct section sec,
|
|
221
|
+
uint32_t vocab_size, const char *name, se_error_t *err) {
|
|
222
|
+
memset(trie, 0, sizeof(*trie));
|
|
223
|
+
if (sec.size < 16) {
|
|
224
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie section too small", name);
|
|
225
|
+
return SE_ERR_INVALID_FORMAT;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const uint8_t *p = base + sec.off;
|
|
229
|
+
uint32_t node_count = read_u32(p, 0);
|
|
230
|
+
uint32_t edge_count = read_u32(p, 4);
|
|
231
|
+
if (node_count == 0) {
|
|
232
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie has no root node", name);
|
|
233
|
+
return SE_ERR_INVALID_FORMAT;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
uint64_t need = 16u + (uint64_t)node_count * sizeof(se_trie_node_t) +
|
|
237
|
+
(uint64_t)edge_count * sizeof(se_trie_edge_t);
|
|
238
|
+
if (need != sec.size) {
|
|
239
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie size mismatch", name);
|
|
240
|
+
return SE_ERR_INVALID_FORMAT;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const se_trie_node_t *nodes = (const se_trie_node_t *)(p + 16);
|
|
244
|
+
const se_trie_edge_t *edges =
|
|
245
|
+
(const se_trie_edge_t *)(p + 16 + (size_t)node_count * sizeof(se_trie_node_t));
|
|
246
|
+
|
|
247
|
+
for (uint32_t i = 0; i < node_count; i++) {
|
|
248
|
+
const se_trie_node_t *node = &nodes[i];
|
|
249
|
+
if (node->token_id != SE_SLOT_EMPTY && node->token_id >= vocab_size) {
|
|
250
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie node %u has token id out of range",
|
|
251
|
+
name, i);
|
|
252
|
+
return SE_ERR_INVALID_FORMAT;
|
|
253
|
+
}
|
|
254
|
+
if (node->edge_start > edge_count || node->edge_count > edge_count - node->edge_start) {
|
|
255
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie node %u edges out of range", name, i);
|
|
256
|
+
return SE_ERR_INVALID_FORMAT;
|
|
257
|
+
}
|
|
258
|
+
uint32_t prev = 0;
|
|
259
|
+
for (uint32_t k = 0; k < node->edge_count; k++) {
|
|
260
|
+
const se_trie_edge_t *edge = &edges[node->edge_start + k];
|
|
261
|
+
if (edge->byte > 255u || edge->child >= node_count) {
|
|
262
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie edge out of range", name);
|
|
263
|
+
return SE_ERR_INVALID_FORMAT;
|
|
264
|
+
}
|
|
265
|
+
if (k && edge->byte <= prev) {
|
|
266
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "%s trie edges are not strictly sorted",
|
|
267
|
+
name);
|
|
268
|
+
return SE_ERR_INVALID_FORMAT;
|
|
269
|
+
}
|
|
270
|
+
prev = edge->byte;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
trie->nodes = nodes;
|
|
275
|
+
trie->edges = edges;
|
|
276
|
+
trie->node_count = node_count;
|
|
277
|
+
trie->edge_count = edge_count;
|
|
278
|
+
return SE_OK;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
static se_status_t parse_norm_tables(se_model_t *model, const uint8_t *base, struct section sec,
|
|
282
|
+
se_error_t *err) {
|
|
283
|
+
memset(&model->norm, 0, sizeof(model->norm));
|
|
284
|
+
if (sec.size == 0)
|
|
285
|
+
return SE_OK;
|
|
286
|
+
|
|
287
|
+
if (sec.size < 32) {
|
|
288
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "norm tables section too small");
|
|
289
|
+
return SE_ERR_INVALID_FORMAT;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const uint8_t *p = base + sec.off;
|
|
293
|
+
uint32_t n_lower = read_u32(p, 0);
|
|
294
|
+
uint32_t n_nfd = read_u32(p, 4);
|
|
295
|
+
uint32_t n_mn = read_u32(p, 8);
|
|
296
|
+
uint32_t n_punct = read_u32(p, 12);
|
|
297
|
+
uint32_t n_control = read_u32(p, 16);
|
|
298
|
+
uint32_t n_ws = read_u32(p, 20);
|
|
299
|
+
|
|
300
|
+
uint64_t map_count = (uint64_t)n_lower + (uint64_t)n_nfd;
|
|
301
|
+
uint64_t range_count =
|
|
302
|
+
(uint64_t)n_mn + (uint64_t)n_punct + (uint64_t)n_control + (uint64_t)n_ws;
|
|
303
|
+
if (map_count > (UINT64_MAX - 32u) / sizeof(se_map_entry_t) ||
|
|
304
|
+
range_count > (UINT64_MAX - 32u) / sizeof(se_range_t)) {
|
|
305
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "norm tables size overflow");
|
|
306
|
+
return SE_ERR_INVALID_FORMAT;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
uint64_t need = 32u + map_count * sizeof(se_map_entry_t) + range_count * sizeof(se_range_t);
|
|
310
|
+
|
|
311
|
+
if (need != sec.size) {
|
|
312
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "norm tables size mismatch (need %llu, have %llu)",
|
|
313
|
+
(unsigned long long)need, (unsigned long long)sec.size);
|
|
314
|
+
return SE_ERR_INVALID_FORMAT;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const uint8_t *cursor = p + 32;
|
|
318
|
+
model->norm.lower = (const se_map_entry_t *)cursor;
|
|
319
|
+
model->norm.lower_count = n_lower;
|
|
320
|
+
cursor += (size_t)n_lower * sizeof(se_map_entry_t);
|
|
321
|
+
|
|
322
|
+
model->norm.nfd = (const se_map_entry_t *)cursor;
|
|
323
|
+
model->norm.nfd_count = n_nfd;
|
|
324
|
+
cursor += (size_t)n_nfd * sizeof(se_map_entry_t);
|
|
325
|
+
|
|
326
|
+
model->norm.mn = (const se_range_t *)cursor;
|
|
327
|
+
model->norm.mn_count = n_mn;
|
|
328
|
+
cursor += (size_t)n_mn * sizeof(se_range_t);
|
|
329
|
+
|
|
330
|
+
model->norm.punct = (const se_range_t *)cursor;
|
|
331
|
+
model->norm.punct_count = n_punct;
|
|
332
|
+
cursor += (size_t)n_punct * sizeof(se_range_t);
|
|
333
|
+
|
|
334
|
+
model->norm.control = (const se_range_t *)cursor;
|
|
335
|
+
model->norm.control_count = n_control;
|
|
336
|
+
cursor += (size_t)n_control * sizeof(se_range_t);
|
|
337
|
+
|
|
338
|
+
model->norm.whitespace = (const se_range_t *)cursor;
|
|
339
|
+
model->norm.whitespace_count = n_ws;
|
|
340
|
+
|
|
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;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
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]);
|
|
398
|
+
return SE_ERR_INVALID_FORMAT;
|
|
399
|
+
}
|
|
400
|
+
prev_hi = range->hi;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return SE_OK;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static se_status_t validate(se_model_t *model, se_error_t *err) {
|
|
408
|
+
const uint8_t *base = (const uint8_t *)model->map_base;
|
|
409
|
+
const uint64_t file_size = (uint64_t)model->map_size;
|
|
410
|
+
|
|
411
|
+
if (file_size < SE_HEADER_SIZE) {
|
|
412
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "file shorter than header");
|
|
413
|
+
return SE_ERR_INVALID_FORMAT;
|
|
414
|
+
}
|
|
415
|
+
if (memcmp(base, SE_MAGIC, SE_MAGIC_LEN) != 0) {
|
|
416
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "bad magic");
|
|
417
|
+
return SE_ERR_INVALID_FORMAT;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
uint32_t version = read_u32(base, SE_OFF_FORMAT_VERSION);
|
|
421
|
+
if (version != SE_FORMAT_VERSION) {
|
|
422
|
+
se_error_set(err, SE_ERR_UNSUPPORTED_VERSION, "format version %u is not supported",
|
|
423
|
+
version);
|
|
424
|
+
return SE_ERR_UNSUPPORTED_VERSION;
|
|
425
|
+
}
|
|
426
|
+
if (read_u32(base, SE_OFF_HEADER_SIZE) != SE_HEADER_SIZE) {
|
|
427
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unexpected header size");
|
|
428
|
+
return SE_ERR_INVALID_FORMAT;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
se_meta_t *m = &model->meta;
|
|
432
|
+
m->dim = read_u32(base, SE_OFF_DIM);
|
|
433
|
+
m->vocab_size = read_u32(base, SE_OFF_VOCAB_SIZE);
|
|
434
|
+
m->tokenizer_type = read_u32(base, SE_OFF_TOKENIZER_TYPE);
|
|
435
|
+
m->embedding_dtype = read_u32(base, SE_OFF_EMBEDDING_DTYPE);
|
|
436
|
+
m->pooling_type = read_u32(base, SE_OFF_POOLING_TYPE);
|
|
437
|
+
m->normalization_type = read_u32(base, SE_OFF_NORMALIZATION_TYPE);
|
|
438
|
+
m->max_tokens_default = read_u32(base, SE_OFF_MAX_TOKENS_DEFAULT);
|
|
439
|
+
m->truncation_policy = read_u32(base, SE_OFF_TRUNCATION_POLICY);
|
|
440
|
+
m->add_special_tokens = read_u32(base, SE_OFF_ADD_SPECIAL_TOKENS);
|
|
441
|
+
m->unk_policy = read_u32(base, SE_OFF_UNK_POLICY);
|
|
442
|
+
m->empty_policy = read_u32(base, SE_OFF_EMPTY_POLICY);
|
|
443
|
+
m->do_lower_case = read_u32(base, SE_OFF_DO_LOWER_CASE);
|
|
444
|
+
m->strip_accents = read_u32(base, SE_OFF_STRIP_ACCENTS);
|
|
445
|
+
m->handle_chinese_chars = read_u32(base, SE_OFF_HANDLE_CHINESE_CHARS);
|
|
446
|
+
m->clean_text = read_u32(base, SE_OFF_CLEAN_TEXT);
|
|
447
|
+
m->max_input_chars_per_word = read_u32(base, SE_OFF_MAX_INPUT_CHARS_PER_WORD);
|
|
448
|
+
m->pad_id = read_u32(base, SE_OFF_PAD_ID);
|
|
449
|
+
m->unk_id = read_u32(base, SE_OFF_UNK_ID);
|
|
450
|
+
m->cls_id = read_u32(base, SE_OFF_CLS_ID);
|
|
451
|
+
m->sep_id = read_u32(base, SE_OFF_SEP_ID);
|
|
452
|
+
m->mask_id = read_u32(base, SE_OFF_MASK_ID);
|
|
453
|
+
m->hash_table_size = read_u32(base, SE_OFF_HASH_TABLE_SIZE);
|
|
454
|
+
m->hash_seed = read_u32(base, SE_OFF_HASH_SEED);
|
|
455
|
+
m->subword_prefix_len = read_u32(base, SE_OFF_SUBWORD_PREFIX_LEN);
|
|
456
|
+
m->max_token_chars = read_u32(base, SE_OFF_MAX_TOKEN_CHARS);
|
|
457
|
+
m->max_probe = read_u32(base, SE_OFF_MAX_PROBE);
|
|
458
|
+
memcpy(m->subword_prefix, base + SE_OFF_SUBWORD_PREFIX, 8);
|
|
459
|
+
|
|
460
|
+
if (m->tokenizer_type != SE_TOKENIZER_BERT_WORDPIECE_V1) {
|
|
461
|
+
se_error_set(err, SE_ERR_UNSUPPORTED_TOKENIZER, "tokenizer profile %u is not supported",
|
|
462
|
+
m->tokenizer_type);
|
|
463
|
+
return SE_ERR_UNSUPPORTED_TOKENIZER;
|
|
464
|
+
}
|
|
465
|
+
if (m->embedding_dtype != SE_DTYPE_F32) {
|
|
466
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "embedding dtype %u is not supported",
|
|
467
|
+
m->embedding_dtype);
|
|
468
|
+
return SE_ERR_INVALID_FORMAT;
|
|
469
|
+
}
|
|
470
|
+
if (m->pooling_type != SE_POOLING_MEAN) {
|
|
471
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "pooling type %u is not supported",
|
|
472
|
+
m->pooling_type);
|
|
473
|
+
return SE_ERR_INVALID_FORMAT;
|
|
474
|
+
}
|
|
475
|
+
if (m->normalization_type != SE_NORMALIZATION_NONE &&
|
|
476
|
+
m->normalization_type != SE_NORMALIZATION_L2) {
|
|
477
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "normalization type %u is not supported",
|
|
478
|
+
m->normalization_type);
|
|
479
|
+
return SE_ERR_INVALID_FORMAT;
|
|
480
|
+
}
|
|
481
|
+
if (m->truncation_policy != SE_TRUNCATE_IDS_BEFORE_POOLING) {
|
|
482
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "truncation policy %u is not supported",
|
|
483
|
+
m->truncation_policy);
|
|
484
|
+
return SE_ERR_INVALID_FORMAT;
|
|
485
|
+
}
|
|
486
|
+
if (m->add_special_tokens != 0) {
|
|
487
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "runtime does not add special tokens");
|
|
488
|
+
return SE_ERR_INVALID_FORMAT;
|
|
489
|
+
}
|
|
490
|
+
if (m->unk_policy != SE_UNK_INCLUDE && m->unk_policy != SE_UNK_DROP) {
|
|
491
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown UNK policy %u", m->unk_policy);
|
|
492
|
+
return SE_ERR_INVALID_FORMAT;
|
|
493
|
+
}
|
|
494
|
+
if (m->empty_policy != SE_EMPTY_ZERO_VECTOR && m->empty_policy != SE_EMPTY_RAISE) {
|
|
495
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unknown empty-input policy %u", m->empty_policy);
|
|
496
|
+
return SE_ERR_INVALID_FORMAT;
|
|
497
|
+
}
|
|
498
|
+
if ((m->do_lower_case > 1) || (m->strip_accents > 1) || (m->handle_chinese_chars > 1) ||
|
|
499
|
+
(m->clean_text > 1)) {
|
|
500
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "normalizer flags must be 0 or 1");
|
|
501
|
+
return SE_ERR_INVALID_FORMAT;
|
|
502
|
+
}
|
|
503
|
+
if (m->dim == 0 || m->dim > (1u << 20)) {
|
|
504
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "implausible dim %u", m->dim);
|
|
505
|
+
return SE_ERR_INVALID_FORMAT;
|
|
506
|
+
}
|
|
507
|
+
if (m->vocab_size == 0) {
|
|
508
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "empty vocabulary");
|
|
509
|
+
return SE_ERR_INVALID_FORMAT;
|
|
510
|
+
}
|
|
511
|
+
if (m->hash_table_size == 0 || (m->hash_table_size & (m->hash_table_size - 1u)) != 0) {
|
|
512
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "hash table size %u is not a power of two",
|
|
513
|
+
m->hash_table_size);
|
|
514
|
+
return SE_ERR_INVALID_FORMAT;
|
|
515
|
+
}
|
|
516
|
+
if (m->hash_table_size < m->vocab_size) {
|
|
517
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "hash table smaller than vocabulary");
|
|
518
|
+
return SE_ERR_INVALID_FORMAT;
|
|
519
|
+
}
|
|
520
|
+
if (m->subword_prefix_len > 8) {
|
|
521
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "subword prefix too long");
|
|
522
|
+
return SE_ERR_INVALID_FORMAT;
|
|
523
|
+
}
|
|
524
|
+
if (m->unk_id >= m->vocab_size) {
|
|
525
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "unk id out of range");
|
|
526
|
+
return SE_ERR_INVALID_FORMAT;
|
|
527
|
+
}
|
|
528
|
+
if (m->clean_text != 1) {
|
|
529
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "BERT_WORDPIECE_V1 requires clean_text=true");
|
|
530
|
+
return SE_ERR_INVALID_FORMAT;
|
|
531
|
+
}
|
|
532
|
+
if (m->max_input_chars_per_word == 0)
|
|
533
|
+
m->max_input_chars_per_word = 100;
|
|
534
|
+
if (m->max_token_chars == 0 || m->max_token_chars > m->max_input_chars_per_word)
|
|
535
|
+
m->max_token_chars = m->max_input_chars_per_word;
|
|
536
|
+
|
|
537
|
+
struct section vocab_strings = read_section(base, SE_OFF_SEC_VOCAB_STRINGS, "vocab_strings");
|
|
538
|
+
struct section vocab_hash = read_section(base, SE_OFF_SEC_VOCAB_HASH, "vocab_hash");
|
|
539
|
+
struct section embeddings = read_section(base, SE_OFF_SEC_EMBEDDINGS, "embeddings");
|
|
540
|
+
struct section norm = read_section(base, SE_OFF_SEC_NORM_TABLES, "norm_tables");
|
|
541
|
+
struct section provenance = read_section(base, SE_OFF_SEC_PROVENANCE, "provenance");
|
|
542
|
+
struct section root_trie = read_section(base, SE_OFF_SEC_ROOT_TRIE, "root_trie");
|
|
543
|
+
struct section cont_trie = read_section(base, SE_OFF_SEC_CONT_TRIE, "continuation_trie");
|
|
544
|
+
|
|
545
|
+
if (!section_ok(vocab_strings, file_size, 0) ||
|
|
546
|
+
!section_ok(vocab_hash, file_size, sizeof(se_vocab_slot_t)) ||
|
|
547
|
+
!section_ok(embeddings, file_size, sizeof(float)) || !section_ok(norm, file_size, 0) ||
|
|
548
|
+
!section_ok(provenance, file_size, 0) || !section_ok(root_trie, file_size, 0) ||
|
|
549
|
+
!section_ok(cont_trie, file_size, 0)) {
|
|
550
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "section offsets are out of bounds or unaligned");
|
|
551
|
+
return SE_ERR_INVALID_FORMAT;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const struct section sections[] = {vocab_strings, vocab_hash, embeddings, norm,
|
|
555
|
+
provenance, root_trie, cont_trie};
|
|
556
|
+
se_status_t overlap_status =
|
|
557
|
+
validate_no_overlaps(sections, sizeof(sections) / sizeof(sections[0]), err);
|
|
558
|
+
if (overlap_status != SE_OK)
|
|
559
|
+
return overlap_status;
|
|
560
|
+
|
|
561
|
+
uint64_t expected_hash_bytes = (uint64_t)m->hash_table_size * sizeof(se_vocab_slot_t);
|
|
562
|
+
if (vocab_hash.size != expected_hash_bytes) {
|
|
563
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "hash section size mismatch");
|
|
564
|
+
return SE_ERR_INVALID_FORMAT;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if ((uint64_t)m->vocab_size > UINT64_MAX / (uint64_t)m->dim ||
|
|
568
|
+
(uint64_t)m->vocab_size * (uint64_t)m->dim > UINT64_MAX / sizeof(float)) {
|
|
569
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT, "embedding matrix size overflow");
|
|
570
|
+
return SE_ERR_INVALID_FORMAT;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
uint64_t expected_matrix = (uint64_t)m->vocab_size * (uint64_t)m->dim * sizeof(float);
|
|
574
|
+
if (embeddings.size != expected_matrix) {
|
|
575
|
+
se_error_set(err, SE_ERR_INVALID_FORMAT,
|
|
576
|
+
"embedding section is %llu bytes, expected vocab_size * dim * 4 = %llu",
|
|
577
|
+
(unsigned long long)embeddings.size, (unsigned long long)expected_matrix);
|
|
578
|
+
return SE_ERR_INVALID_FORMAT;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
model->vocab_strings = (const char *)(base + vocab_strings.off);
|
|
582
|
+
model->vocab_strings_size = (size_t)vocab_strings.size;
|
|
583
|
+
model->vocab_hash = (const se_vocab_slot_t *)(base + vocab_hash.off);
|
|
584
|
+
model->embeddings = (const float *)(base + embeddings.off);
|
|
585
|
+
model->provenance = provenance.size ? (const char *)(base + provenance.off) : NULL;
|
|
586
|
+
model->provenance_size = (size_t)provenance.size;
|
|
587
|
+
|
|
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
|
+
}
|
|
628
|
+
|
|
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
|
+
}
|
|
656
|
+
|
|
657
|
+
se_status_t trie_status =
|
|
658
|
+
parse_trie(&model->root_trie, base, root_trie, m->vocab_size, "root", err);
|
|
659
|
+
if (trie_status != SE_OK)
|
|
660
|
+
return trie_status;
|
|
661
|
+
trie_status =
|
|
662
|
+
parse_trie(&model->cont_trie, base, cont_trie, m->vocab_size, "continuation", err);
|
|
663
|
+
if (trie_status != SE_OK)
|
|
664
|
+
return trie_status;
|
|
665
|
+
|
|
666
|
+
return parse_norm_tables(model, base, norm, err);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
se_status_t se_model_open(se_model_t *model, const char *path, se_error_t *err) {
|
|
670
|
+
memset(model, 0, sizeof(*model));
|
|
671
|
+
se_error_clear(err);
|
|
672
|
+
|
|
673
|
+
#ifndef _WIN32
|
|
674
|
+
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
|
675
|
+
if (fd < 0) {
|
|
676
|
+
se_error_set(err, SE_ERR_IO, "cannot open %s", path);
|
|
677
|
+
return SE_ERR_IO;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
struct stat st;
|
|
681
|
+
if (fstat(fd, &st) != 0 || st.st_size <= 0) {
|
|
682
|
+
close(fd);
|
|
683
|
+
se_error_set(err, SE_ERR_IO, "cannot stat %s", path);
|
|
684
|
+
return SE_ERR_IO;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if ((uintmax_t)st.st_size > (uintmax_t)SIZE_MAX) {
|
|
688
|
+
close(fd);
|
|
689
|
+
se_error_set(err, SE_ERR_IO, "file is too large to map safely");
|
|
690
|
+
return SE_ERR_IO;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
void *addr = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
694
|
+
close(fd);
|
|
695
|
+
if (addr == MAP_FAILED) {
|
|
696
|
+
se_error_set(err, SE_ERR_IO, "mmap failed for %s", path);
|
|
697
|
+
return SE_ERR_IO;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
model->map_base = addr;
|
|
701
|
+
model->map_size = (size_t)st.st_size;
|
|
702
|
+
model->mapped = 1;
|
|
703
|
+
#else
|
|
704
|
+
FILE *f = fopen(path, "rb");
|
|
705
|
+
if (!f) {
|
|
706
|
+
se_error_set(err, SE_ERR_IO, "cannot open %s", path);
|
|
707
|
+
return SE_ERR_IO;
|
|
708
|
+
}
|
|
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);
|
|
715
|
+
return SE_ERR_IO;
|
|
716
|
+
}
|
|
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);
|
|
727
|
+
return SE_ERR_IO;
|
|
728
|
+
}
|
|
729
|
+
fclose(f);
|
|
730
|
+
model->map_base = buf;
|
|
731
|
+
model->map_size = (size_t)size;
|
|
732
|
+
model->mapped = 0;
|
|
733
|
+
#endif
|
|
734
|
+
|
|
735
|
+
se_status_t rc = validate(model, err);
|
|
736
|
+
if (rc != SE_OK) {
|
|
737
|
+
se_model_close(model);
|
|
738
|
+
return rc;
|
|
739
|
+
}
|
|
740
|
+
return SE_OK;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
void se_model_close(se_model_t *model) {
|
|
744
|
+
if (!model || !model->map_base)
|
|
745
|
+
return;
|
|
746
|
+
#ifndef _WIN32
|
|
747
|
+
if (model->mapped)
|
|
748
|
+
munmap(model->map_base, model->map_size);
|
|
749
|
+
else
|
|
750
|
+
free(model->map_base);
|
|
751
|
+
#else
|
|
752
|
+
free(model->map_base);
|
|
753
|
+
#endif
|
|
754
|
+
memset(model, 0, sizeof(*model));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
size_t se_model_memsize(const se_model_t *model) {
|
|
758
|
+
if (!model)
|
|
759
|
+
return 0;
|
|
760
|
+
|
|
761
|
+
return sizeof(se_model_t) + model->map_size;
|
|
762
|
+
}
|