| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #ifndef SAFETENSORS_READER_H |
| #define SAFETENSORS_READER_H |
|
|
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
|
|
| |
| |
| |
|
|
| #define ST_MAX_TENSORS 4096 |
| #define ST_MAX_NAME_LEN 256 |
| #define ST_MAX_DIMS 8 |
| #define ST_MAX_HEADER_SIZE (100 * 1024 * 1024) |
|
|
| |
| |
| |
|
|
| typedef enum { |
| ST_DTYPE_F32, |
| ST_DTYPE_F16, |
| ST_DTYPE_BF16, |
| ST_DTYPE_F64, |
| ST_DTYPE_I8, |
| ST_DTYPE_I16, |
| ST_DTYPE_I32, |
| ST_DTYPE_I64, |
| ST_DTYPE_U8, |
| ST_DTYPE_BOOL, |
| ST_DTYPE_UNKNOWN |
| } STDtype; |
|
|
| static inline int st_dtype_size(STDtype dtype) |
| { |
| switch (dtype) { |
| case ST_DTYPE_F32: return 4; |
| case ST_DTYPE_F16: return 2; |
| case ST_DTYPE_BF16: return 2; |
| case ST_DTYPE_F64: return 8; |
| case ST_DTYPE_I8: return 1; |
| case ST_DTYPE_I16: return 2; |
| case ST_DTYPE_I32: return 4; |
| case ST_DTYPE_I64: return 8; |
| case ST_DTYPE_U8: return 1; |
| case ST_DTYPE_BOOL: return 1; |
| default: return 0; |
| } |
| } |
|
|
| static inline STDtype st_parse_dtype(const char *s, int len) |
| { |
| if (len == 3 && strncmp(s, "F32", 3) == 0) return ST_DTYPE_F32; |
| if (len == 3 && strncmp(s, "F16", 3) == 0) return ST_DTYPE_F16; |
| if (len == 4 && strncmp(s, "BF16", 4) == 0) return ST_DTYPE_BF16; |
| if (len == 3 && strncmp(s, "F64", 3) == 0) return ST_DTYPE_F64; |
| if (len == 2 && strncmp(s, "I8", 2) == 0) return ST_DTYPE_I8; |
| if (len == 3 && strncmp(s, "I16", 3) == 0) return ST_DTYPE_I16; |
| if (len == 3 && strncmp(s, "I32", 3) == 0) return ST_DTYPE_I32; |
| if (len == 3 && strncmp(s, "I64", 3) == 0) return ST_DTYPE_I64; |
| if (len == 2 && strncmp(s, "U8", 2) == 0) return ST_DTYPE_U8; |
| if (len == 4 && strncmp(s, "BOOL", 4) == 0) return ST_DTYPE_BOOL; |
| return ST_DTYPE_UNKNOWN; |
| } |
|
|
| |
| |
| |
|
|
| typedef struct { |
| char name[ST_MAX_NAME_LEN]; |
| STDtype dtype; |
| int n_dims; |
| int64_t shape[ST_MAX_DIMS]; |
| int64_t n_elements; |
| uint64_t data_offset_begin; |
| uint64_t data_offset_end; |
| uint64_t data_size; |
| } STTensorInfo; |
|
|
| |
| |
| |
|
|
| typedef struct { |
| |
| int fd; |
| uint8_t *mmap_base; |
| size_t file_size; |
|
|
| |
| uint64_t header_size; |
| char *header_json; |
| |
|
|
| |
| uint8_t *data_base; |
|
|
| |
| STTensorInfo tensors[ST_MAX_TENSORS]; |
| int n_tensors; |
| } STFile; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| static inline const char *st_skip_ws(const char *p) |
| { |
| while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; |
| return p; |
| } |
|
|
| |
| |
| static inline const char *st_parse_json_string(const char *p, char *buf, int buflen) |
| { |
| if (*p != '"') return NULL; |
| p++; |
| int i = 0; |
| while (*p && *p != '"') { |
| if (*p == '\\') { |
| p++; |
| if (!*p) return NULL; |
| } |
| if (i < buflen - 1) buf[i++] = *p; |
| p++; |
| } |
| buf[i] = '\0'; |
| if (*p == '"') p++; |
| return p; |
| } |
|
|
| |
| static inline const char *st_parse_json_int(const char *p, int64_t *out) |
| { |
| char numbuf[32]; |
| int i = 0; |
| if (*p == '-') { numbuf[i++] = *p; p++; } |
| while (*p >= '0' && *p <= '9' && i < 30) { |
| numbuf[i++] = *p; |
| p++; |
| } |
| numbuf[i] = '\0'; |
| *out = strtoll(numbuf, NULL, 10); |
| return p; |
| } |
|
|
| |
| static inline const char *st_skip_json_value(const char *p) |
| { |
| p = st_skip_ws(p); |
| if (*p == '"') { |
| |
| p++; |
| while (*p && *p != '"') { |
| if (*p == '\\') p++; |
| if (*p) p++; |
| } |
| if (*p == '"') p++; |
| return p; |
| } |
| if (*p == '{') { |
| |
| int depth = 1; |
| p++; |
| while (*p && depth > 0) { |
| if (*p == '{') depth++; |
| else if (*p == '}') depth--; |
| else if (*p == '"') { |
| p++; |
| while (*p && *p != '"') { |
| if (*p == '\\') p++; |
| if (*p) p++; |
| } |
| } |
| if (*p) p++; |
| } |
| return p; |
| } |
| if (*p == '[') { |
| |
| int depth = 1; |
| p++; |
| while (*p && depth > 0) { |
| if (*p == '[') depth++; |
| else if (*p == ']') depth--; |
| else if (*p == '"') { |
| p++; |
| while (*p && *p != '"') { |
| if (*p == '\\') p++; |
| if (*p) p++; |
| } |
| } |
| if (*p) p++; |
| } |
| return p; |
| } |
| |
| while (*p && *p != ',' && *p != '}' && *p != ']' && |
| *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') { |
| p++; |
| } |
| return p; |
| } |
|
|
| |
| static inline int st_parse_header(STFile *st) |
| { |
| const char *p = st->header_json; |
| p = st_skip_ws(p); |
| if (*p != '{') return -1; |
| p++; |
|
|
| st->n_tensors = 0; |
|
|
| while (*p) { |
| p = st_skip_ws(p); |
| if (*p == '}') break; |
| if (*p == ',') { p++; continue; } |
|
|
| |
| char key[ST_MAX_NAME_LEN]; |
| p = st_parse_json_string(p, key, sizeof(key)); |
| if (!p) return -1; |
|
|
| p = st_skip_ws(p); |
| if (*p != ':') return -1; |
| p++; |
| p = st_skip_ws(p); |
|
|
| |
| if (strcmp(key, "__metadata__") == 0) { |
| p = st_skip_json_value(p); |
| continue; |
| } |
|
|
| |
| if (*p != '{') { |
| p = st_skip_json_value(p); |
| continue; |
| } |
| p++; |
|
|
| STTensorInfo *ti = &st->tensors[st->n_tensors]; |
| memset(ti, 0, sizeof(*ti)); |
| strncpy(ti->name, key, ST_MAX_NAME_LEN - 1); |
|
|
| while (*p) { |
| p = st_skip_ws(p); |
| if (*p == '}') { p++; break; } |
| if (*p == ',') { p++; continue; } |
|
|
| char field[64]; |
| p = st_parse_json_string(p, field, sizeof(field)); |
| if (!p) return -1; |
|
|
| p = st_skip_ws(p); |
| if (*p != ':') return -1; |
| p++; |
| p = st_skip_ws(p); |
|
|
| if (strcmp(field, "dtype") == 0) { |
| char dtype_str[16]; |
| p = st_parse_json_string(p, dtype_str, sizeof(dtype_str)); |
| if (!p) return -1; |
| ti->dtype = st_parse_dtype(dtype_str, strlen(dtype_str)); |
| } else if (strcmp(field, "shape") == 0) { |
| |
| if (*p != '[') return -1; |
| p++; |
| ti->n_dims = 0; |
| ti->n_elements = 1; |
| while (*p) { |
| p = st_skip_ws(p); |
| if (*p == ']') { p++; break; } |
| if (*p == ',') { p++; continue; } |
| int64_t dim_val; |
| p = st_parse_json_int(p, &dim_val); |
| if (ti->n_dims < ST_MAX_DIMS) { |
| ti->shape[ti->n_dims++] = dim_val; |
| ti->n_elements *= dim_val; |
| } |
| } |
| } else if (strcmp(field, "data_offsets") == 0) { |
| |
| if (*p != '[') return -1; |
| p++; |
| p = st_skip_ws(p); |
| int64_t begin_val, end_val; |
| p = st_parse_json_int(p, &begin_val); |
| p = st_skip_ws(p); |
| if (*p == ',') p++; |
| p = st_skip_ws(p); |
| p = st_parse_json_int(p, &end_val); |
| p = st_skip_ws(p); |
| if (*p == ']') p++; |
| ti->data_offset_begin = (uint64_t)begin_val; |
| ti->data_offset_end = (uint64_t)end_val; |
| ti->data_size = ti->data_offset_end - ti->data_offset_begin; |
| } else { |
| p = st_skip_json_value(p); |
| } |
| } |
|
|
| if (st->n_tensors < ST_MAX_TENSORS) |
| st->n_tensors++; |
| } |
|
|
| return 0; |
| } |
|
|
| |
| |
| |
|
|
| static inline STFile *st_open(const char *path) |
| { |
| STFile *st = (STFile *)calloc(1, sizeof(STFile)); |
| if (!st) return NULL; |
|
|
| |
| st->fd = open(path, O_RDONLY); |
| if (st->fd < 0) { |
| fprintf(stderr, "st_open: cannot open '%s'\n", path); |
| free(st); |
| return NULL; |
| } |
|
|
| |
| struct stat sb; |
| if (fstat(st->fd, &sb) < 0) { |
| close(st->fd); |
| free(st); |
| return NULL; |
| } |
| st->file_size = sb.st_size; |
|
|
| |
| st->mmap_base = (uint8_t *)mmap(NULL, st->file_size, PROT_READ, |
| MAP_PRIVATE, st->fd, 0); |
| if (st->mmap_base == MAP_FAILED) { |
| fprintf(stderr, "st_open: mmap failed for '%s'\n", path); |
| close(st->fd); |
| free(st); |
| return NULL; |
| } |
|
|
| |
| memcpy(&st->header_size, st->mmap_base, sizeof(uint64_t)); |
|
|
| if (st->header_size > ST_MAX_HEADER_SIZE || |
| st->header_size + 8 > st->file_size) { |
| fprintf(stderr, "st_open: invalid header size %lu\n", |
| (unsigned long)st->header_size); |
| munmap(st->mmap_base, st->file_size); |
| close(st->fd); |
| free(st); |
| return NULL; |
| } |
|
|
| |
| st->header_json = (char *)malloc(st->header_size + 1); |
| memcpy(st->header_json, st->mmap_base + 8, st->header_size); |
| st->header_json[st->header_size] = '\0'; |
|
|
| |
| st->data_base = st->mmap_base + 8 + st->header_size; |
|
|
| |
| if (st_parse_header(st) != 0) { |
| fprintf(stderr, "st_open: failed to parse header of '%s'\n", path); |
| free(st->header_json); |
| munmap(st->mmap_base, st->file_size); |
| close(st->fd); |
| free(st); |
| return NULL; |
| } |
|
|
| return st; |
| } |
|
|
| static inline void st_close(STFile *st) |
| { |
| if (!st) return; |
| free(st->header_json); |
| if (st->mmap_base && st->mmap_base != MAP_FAILED) |
| munmap(st->mmap_base, st->file_size); |
| if (st->fd >= 0) |
| close(st->fd); |
| free(st); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| static inline const void *st_tensor_data(const STFile *st, int tensor_idx) |
| { |
| if (tensor_idx < 0 || tensor_idx >= st->n_tensors) return NULL; |
| return st->data_base + st->tensors[tensor_idx].data_offset_begin; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| static inline float *st_tensor_to_f32(const STFile *st, int tensor_idx) |
| { |
| const STTensorInfo *ti = &st->tensors[tensor_idx]; |
| const uint8_t *raw = (const uint8_t *)st_tensor_data(st, tensor_idx); |
| if (!raw) return NULL; |
|
|
| float *out = (float *)malloc(ti->n_elements * sizeof(float)); |
| if (!out) return NULL; |
|
|
| switch (ti->dtype) { |
| case ST_DTYPE_F32: |
| memcpy(out, raw, ti->n_elements * sizeof(float)); |
| break; |
|
|
| case ST_DTYPE_F16: { |
| const uint16_t *fp16 = (const uint16_t *)raw; |
| for (int64_t i = 0; i < ti->n_elements; i++) { |
| out[i] = gguf_fp16_to_fp32(fp16[i]); |
| } |
| break; |
| } |
|
|
| case ST_DTYPE_BF16: { |
| const uint16_t *bf16 = (const uint16_t *)raw; |
| for (int64_t i = 0; i < ti->n_elements; i++) { |
| out[i] = gguf_bf16_to_fp32(bf16[i]); |
| } |
| break; |
| } |
|
|
| case ST_DTYPE_F64: { |
| const double *f64 = (const double *)raw; |
| for (int64_t i = 0; i < ti->n_elements; i++) { |
| out[i] = (float)f64[i]; |
| } |
| break; |
| } |
|
|
| default: |
| |
| for (int64_t i = 0; i < ti->n_elements; i++) { |
| switch (ti->dtype) { |
| case ST_DTYPE_I8: out[i] = (float)((int8_t *)raw)[i]; break; |
| case ST_DTYPE_I16: out[i] = (float)((int16_t *)raw)[i]; break; |
| case ST_DTYPE_I32: out[i] = (float)((int32_t *)raw)[i]; break; |
| case ST_DTYPE_U8: out[i] = (float)raw[i]; break; |
| default: out[i] = 0.0f; break; |
| } |
| } |
| break; |
| } |
|
|
| return out; |
| } |
|
|
| |
| |
| |
|
|
| static inline int st_find_tensor(const STFile *st, const char *name) |
| { |
| for (int i = 0; i < st->n_tensors; i++) { |
| if (strcmp(st->tensors[i].name, name) == 0) |
| return i; |
| } |
| return -1; |
| } |
|
|
| |
| |
| |
|
|
| static inline void st_print_summary(const STFile *st) |
| { |
| printf(" βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n"); |
| printf(" β SafeTensors File Summary β\n"); |
| printf(" β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£\n"); |
| printf(" β File size: %12lu bytes β\n", |
| (unsigned long)st->file_size); |
| printf(" β Header size: %12lu bytes β\n", |
| (unsigned long)st->header_size); |
| printf(" β Tensors: %12d β\n", |
| st->n_tensors); |
| printf(" βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n"); |
|
|
| const char *dtype_names[] = { |
| "F32", "F16", "BF16", "F64", "I8", "I16", "I32", "I64", |
| "U8", "BOOL", "???" |
| }; |
|
|
| for (int i = 0; i < st->n_tensors; i++) { |
| const STTensorInfo *ti = &st->tensors[i]; |
| printf(" [%3d] %-50s %4s [", i, ti->name, |
| dtype_names[ti->dtype < ST_DTYPE_UNKNOWN ? ti->dtype : ST_DTYPE_UNKNOWN]); |
| for (int d = 0; d < ti->n_dims; d++) { |
| printf("%ld%s", (long)ti->shape[d], d < ti->n_dims - 1 ? "Γ" : ""); |
| } |
| printf("] %lu bytes\n", (unsigned long)ti->data_size); |
| } |
| printf("\n"); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <dirent.h> |
|
|
| #define ST_MAX_SHARDS 256 |
|
|
| typedef struct { |
| STFile *shards[ST_MAX_SHARDS]; |
| int n_shards; |
|
|
| |
| struct { |
| char name[ST_MAX_NAME_LEN]; |
| int shard_idx; |
| int tensor_idx; |
| } tensor_map[ST_MAX_TENSORS]; |
| int n_tensors; |
| } STMultiFile; |
|
|
| |
| static int st_cmp_str(const void *a, const void *b) |
| { |
| return strcmp(*(const char **)a, *(const char **)b); |
| } |
|
|
| |
| |
| |
| static STMultiFile *st_open_dir(const char *model_dir) |
| { |
| STMultiFile *mf = (STMultiFile *)calloc(1, sizeof(STMultiFile)); |
| if (!mf) return NULL; |
|
|
| |
| char dir[512]; |
| strncpy(dir, model_dir, sizeof(dir) - 2); |
| dir[sizeof(dir) - 2] = '\0'; |
| int dlen = strlen(dir); |
| if (dlen > 0 && dir[dlen - 1] != '/') { |
| dir[dlen] = '/'; |
| dir[dlen + 1] = '\0'; |
| } |
|
|
| |
| char single_path[1024]; |
| snprintf(single_path, sizeof(single_path), "%smodel.safetensors", dir); |
| { |
| FILE *check = fopen(single_path, "rb"); |
| if (check) { |
| fclose(check); |
| STFile *sf = st_open(single_path); |
| if (sf) { |
| mf->shards[0] = sf; |
| mf->n_shards = 1; |
| |
| for (int i = 0; i < sf->n_tensors && mf->n_tensors < ST_MAX_TENSORS; i++) { |
| strncpy(mf->tensor_map[mf->n_tensors].name, |
| sf->tensors[i].name, ST_MAX_NAME_LEN - 1); |
| mf->tensor_map[mf->n_tensors].shard_idx = 0; |
| mf->tensor_map[mf->n_tensors].tensor_idx = i; |
| mf->n_tensors++; |
| } |
| return mf; |
| } |
| } |
| } |
|
|
| |
| DIR *d = opendir(model_dir); |
| if (!d) { |
| fprintf(stderr, " st_open_dir: cannot open directory '%s'\n", model_dir); |
| free(mf); |
| return NULL; |
| } |
|
|
| char *shard_names[ST_MAX_SHARDS]; |
| int n_found = 0; |
| struct dirent *de; |
|
|
| while ((de = readdir(d)) != NULL && n_found < ST_MAX_SHARDS) { |
| int nlen = strlen(de->d_name); |
| if (nlen > 12 && strcmp(de->d_name + nlen - 12, ".safetensors") == 0) { |
| |
| if (strstr(de->d_name, ".index.json") != NULL) continue; |
| shard_names[n_found] = strdup(de->d_name); |
| n_found++; |
| } |
| } |
| closedir(d); |
|
|
| if (n_found == 0) { |
| fprintf(stderr, " st_open_dir: no .safetensors files in '%s'\n", model_dir); |
| free(mf); |
| return NULL; |
| } |
|
|
| |
| qsort(shard_names, n_found, sizeof(char *), st_cmp_str); |
|
|
| |
| for (int s = 0; s < n_found; s++) { |
| char path[1024]; |
| snprintf(path, sizeof(path), "%s%s", dir, shard_names[s]); |
|
|
| STFile *sf = st_open(path); |
| if (!sf) { |
| fprintf(stderr, " st_open_dir: failed to open shard '%s'\n", path); |
| free(shard_names[s]); |
| continue; |
| } |
|
|
| int si = mf->n_shards; |
| mf->shards[si] = sf; |
|
|
| |
| for (int i = 0; i < sf->n_tensors && mf->n_tensors < ST_MAX_TENSORS; i++) { |
| strncpy(mf->tensor_map[mf->n_tensors].name, |
| sf->tensors[i].name, ST_MAX_NAME_LEN - 1); |
| mf->tensor_map[mf->n_tensors].shard_idx = si; |
| mf->tensor_map[mf->n_tensors].tensor_idx = i; |
| mf->n_tensors++; |
| } |
|
|
| mf->n_shards++; |
| free(shard_names[s]); |
| } |
|
|
| if (mf->n_shards == 0) { |
| free(mf); |
| return NULL; |
| } |
|
|
| printf(" Opened %d shards, %d tensors total\n\n", mf->n_shards, mf->n_tensors); |
| return mf; |
| } |
|
|
| |
| |
| static int st_multi_find_tensor(const STMultiFile *mf, const char *name) |
| { |
| for (int i = 0; i < mf->n_tensors; i++) { |
| if (strcmp(mf->tensor_map[i].name, name) == 0) |
| return i; |
| } |
| return -1; |
| } |
|
|
| |
| static const STTensorInfo *st_multi_tensor_info(const STMultiFile *mf, int unified_idx) |
| { |
| if (unified_idx < 0 || unified_idx >= mf->n_tensors) return NULL; |
| int si = mf->tensor_map[unified_idx].shard_idx; |
| int ti = mf->tensor_map[unified_idx].tensor_idx; |
| return &mf->shards[si]->tensors[ti]; |
| } |
|
|
| |
| static float *st_multi_tensor_to_f32(const STMultiFile *mf, int unified_idx) |
| { |
| if (unified_idx < 0 || unified_idx >= mf->n_tensors) return NULL; |
| int si = mf->tensor_map[unified_idx].shard_idx; |
| int ti = mf->tensor_map[unified_idx].tensor_idx; |
| return st_tensor_to_f32(mf->shards[si], ti); |
| } |
|
|
| |
| static const void *st_multi_tensor_data(const STMultiFile *mf, int unified_idx) |
| { |
| if (unified_idx < 0 || unified_idx >= mf->n_tensors) return NULL; |
| int si = mf->tensor_map[unified_idx].shard_idx; |
| int ti = mf->tensor_map[unified_idx].tensor_idx; |
| return st_tensor_data(mf->shards[si], ti); |
| } |
|
|
| static void st_multi_close(STMultiFile *mf) |
| { |
| if (!mf) return; |
| for (int i = 0; i < mf->n_shards; i++) |
| st_close(mf->shards[i]); |
| free(mf); |
| } |
|
|
| static void st_multi_print_summary(const STMultiFile *mf) |
| { |
| printf(" βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n"); |
| printf(" β SafeTensors Multi-Shard Summary β\n"); |
| printf(" β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£\n"); |
| printf(" β Shards: %12d β\n", |
| mf->n_shards); |
|
|
| uint64_t total_size = 0; |
| for (int s = 0; s < mf->n_shards; s++) |
| total_size += mf->shards[s]->file_size; |
| printf(" β Total size: %12lu bytes (%6.1f MB) β\n", |
| (unsigned long)total_size, (double)total_size / (1024.0 * 1024.0)); |
| printf(" β Tensors: %12d β\n", |
| mf->n_tensors); |
| printf(" βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n"); |
|
|
| const char *dtype_names[] = { |
| "F32", "F16", "BF16", "F64", "I8", "I16", "I32", "I64", |
| "U8", "BOOL", "???" |
| }; |
|
|
| for (int i = 0; i < mf->n_tensors; i++) { |
| const STTensorInfo *ti = st_multi_tensor_info(mf, i); |
| printf(" [%3d] s%-2d %-48s %4s [", i, |
| mf->tensor_map[i].shard_idx, ti->name, |
| dtype_names[ti->dtype < ST_DTYPE_UNKNOWN ? ti->dtype : ST_DTYPE_UNKNOWN]); |
| for (int d = 0; d < ti->n_dims; d++) { |
| printf("%ld%s", (long)ti->shape[d], d < ti->n_dims - 1 ? "Γ" : ""); |
| } |
| printf("] %lu bytes\n", (unsigned long)ti->data_size); |
| } |
| printf("\n"); |
| } |
|
|
| #endif |
|
|