Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_pth.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27
28#include <stdint.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include "nilorea/n_log.h"
34#include "nilorea/n_pth.h"
35#include "nilorea/n_zip.h"
36
39#define PTH_PICKLE_MAX (64u * 1024u * 1024u)
41#define PTH_STORAGE_MAX ((size_t)4 * 1024u * 1024u * 1024u)
44#define PTH_OBJECTS_MAX 4000000u
45
63
66typedef struct PK_OBJ {
68 int type;
70 int64_t i;
72 double f;
74 char* s;
78 const char* dtype;
81 struct PK_OBJ** items;
83 size_t n;
85 size_t cap;
86} PK_OBJ;
87
89typedef struct {
93 size_t all_n;
95 size_t all_cap;
99 size_t sp;
101 size_t stack_cap;
105 size_t memo_n;
106} PK_VM;
107
112static PK_OBJ* pk_new(PK_VM* vm, int type) {
113 PK_OBJ* o = NULL;
114
115 if (vm->all_n >= PTH_OBJECTS_MAX) return NULL;
116 if (vm->all_n == vm->all_cap) {
117 size_t cap = vm->all_cap ? vm->all_cap * 2 : 256;
118 PK_OBJ** grown = (PK_OBJ**)realloc((void*)vm->all, cap * sizeof(*grown));
119
120 if (!grown) return NULL;
121 vm->all = grown;
122 vm->all_cap = cap;
123 }
124 o = (PK_OBJ*)calloc(1, sizeof(*o));
125 if (!o) return NULL;
126 o->type = type;
127 vm->all[vm->all_n++] = o;
128 return o;
129}
130
135static int pk_add(PK_OBJ* o, PK_OBJ* item) {
136 if (o->n == o->cap) {
137 size_t cap = o->cap ? o->cap * 2 : 4;
138 PK_OBJ** grown = NULL;
139
140 if (cap > PTH_OBJECTS_MAX) return -1;
141 grown = (PK_OBJ**)realloc((void*)o->items, cap * sizeof(*grown));
142 if (!grown) return -1;
143 o->items = grown;
144 o->cap = cap;
145 }
146 o->items[o->n++] = item;
147 return 0;
148}
149
154static int pk_push(PK_VM* vm, PK_OBJ* o) {
155 if (!o) return -1;
156 if (vm->sp == vm->stack_cap) {
157 size_t cap = vm->stack_cap ? vm->stack_cap * 2 : 64;
158 PK_OBJ** grown = NULL;
159
160 if (cap > PTH_OBJECTS_MAX) return -1;
161 grown = (PK_OBJ**)realloc((void*)vm->stack, cap * sizeof(*grown));
162 if (!grown) return -1;
163 vm->stack = grown;
164 vm->stack_cap = cap;
165 }
166 vm->stack[vm->sp++] = o;
167 return 0;
168}
169
173static PK_OBJ* pk_pop(PK_VM* vm) {
174 if (!vm->sp || vm->stack[vm->sp - 1]->type == PK_MARK) return NULL;
175 return vm->stack[--vm->sp];
176}
177
181static long pk_mark(const PK_VM* vm) {
182 size_t i = vm->sp;
183
184 while (i > 0) {
185 if (vm->stack[i - 1]->type == PK_MARK) return (long)(i - 1);
186 i--;
187 }
188 return -1;
189}
190
197static PK_OBJ* pk_string(PK_VM* vm, int type, const unsigned char* p, size_t len) {
198 PK_OBJ* o = pk_new(vm, type);
199
200 if (!o) return NULL;
201 o->s = (char*)malloc(len + 1);
202 if (!o->s) return NULL;
203 memcpy(o->s, p, len);
204 o->s[len] = '\0';
205 o->i = (int64_t)len;
206 return o;
207}
208
214static int pk_memo_put(PK_VM* vm, size_t index, PK_OBJ* o) {
215 if (!o || index >= PTH_OBJECTS_MAX) return -1;
216 if (index >= vm->memo_n) {
217 size_t n = index + 1 > vm->memo_n * 2 ? index + 1 : vm->memo_n * 2;
218 PK_OBJ** grown = (PK_OBJ**)realloc((void*)vm->memo, n * sizeof(*grown));
219
220 if (!grown) return -1;
221 memset((void*)(grown + vm->memo_n), 0, (n - vm->memo_n) * sizeof(*grown));
222 vm->memo = grown;
223 vm->memo_n = n;
224 }
225 vm->memo[index] = o;
226 return 0;
227}
228
233static uint64_t le(const unsigned char* p, int n) {
234 uint64_t v = 0;
235 int k = 0;
236
237 for (k = n - 1; k >= 0; k--) v = (v << 8) | p[k];
238 return v;
239}
240
245static int storage_element(const char* name, const char** dtype) {
246 static const struct {
247 const char* global;
248 const char* dtype;
249 int size;
250 } known[] = {{"torch FloatStorage", "float32", 4},
251 {"torch HalfStorage", "float16", 2},
252 {"torch BFloat16Storage", "bfloat16", 2},
253 {"torch DoubleStorage", "float64", 8},
254 {NULL, NULL, 0}};
255 int i = 0;
256
257 for (i = 0; known[i].global; i++) {
258 if (strcmp(name, known[i].global) == 0) {
259 *dtype = known[i].dtype;
260 return known[i].size;
261 }
262 }
263 *dtype = name;
264 return 0;
265}
266
272static PK_OBJ* pk_reduce(PK_VM* vm, const PK_OBJ* callable, PK_OBJ* args) {
273 if (callable->type == PK_GLOBAL && strcmp(callable->s, "collections OrderedDict") == 0) return pk_new(vm, PK_DICT);
274 if (callable->type == PK_GLOBAL && (strcmp(callable->s, "torch._utils _rebuild_tensor_v2") == 0 ||
275 strcmp(callable->s, "torch._utils _rebuild_tensor") == 0)) {
276 PK_OBJ* t = NULL;
277
278 /* (storage, offset, size, stride, ...) */
279 if (args->type != PK_TUPLE || args->n < 4 || args->items[0]->type != PK_STORAGE ||
280 args->items[1]->type != PK_INT || args->items[2]->type != PK_TUPLE || args->items[3]->type != PK_TUPLE)
281 return pk_new(vm, PK_OPAQUE);
282 t = pk_new(vm, PK_TENSOR);
283 if (!t) return NULL;
284 t->i = args->items[1]->i;
285 if (pk_add(t, args->items[0]) || pk_add(t, args->items[2]) || pk_add(t, args->items[3])) return NULL;
286 return t;
287 }
288 if (callable->type == PK_GLOBAL && strcmp(callable->s, "torch._utils _rebuild_parameter") == 0 &&
289 args->type == PK_TUPLE && args->n >= 1 && args->items[0]->type == PK_TENSOR)
290 return args->items[0];
291 return pk_new(vm, PK_OPAQUE);
292}
293
298static PK_OBJ* pk_from_mark(PK_VM* vm, int type) {
299 long m = pk_mark(vm);
300 PK_OBJ* o = NULL;
301 size_t k = 0;
302
303 if (m < 0) return NULL;
304 o = pk_new(vm, type);
305 if (!o) return NULL;
306 for (k = (size_t)m + 1; k < vm->sp; k++)
307 if (pk_add(o, vm->stack[k])) return NULL;
308 vm->sp = (size_t)m;
309 return o;
310}
311
317static PK_OBJ* pk_run(PK_VM* vm, const unsigned char* p, size_t len) {
318 size_t at = 0;
319
320/* How many bytes are left, for the checks. */
321#define LEFT (len - at)
322 while (at < len) {
323 unsigned char op = p[at++];
324 PK_OBJ *a = NULL, *b = NULL, *c = NULL;
325
326 switch (op) {
327 case 0x80: /* PROTO */
328 if (LEFT < 1) return NULL;
329 at += 1;
330 break;
331 case 0x95: /* FRAME */
332 if (LEFT < 8) return NULL;
333 at += 8;
334 break;
335 case '.': /* STOP */
336 return pk_pop(vm);
337 case '(': /* MARK */
338 if (pk_push(vm, pk_new(vm, PK_MARK))) return NULL;
339 break;
340 case '0': /* POP */
341 if (!vm->sp) return NULL;
342 vm->sp--;
343 break;
344 case '1': { /* POP_MARK */
345 long m = pk_mark(vm);
346
347 if (m < 0) return NULL;
348 vm->sp = (size_t)m;
349 break;
350 }
351 case '}': /* EMPTY_DICT */
352 if (pk_push(vm, pk_new(vm, PK_DICT))) return NULL;
353 break;
354 case ']': /* EMPTY_LIST */
355 if (pk_push(vm, pk_new(vm, PK_LIST))) return NULL;
356 break;
357 case ')': /* EMPTY_TUPLE */
358 if (pk_push(vm, pk_new(vm, PK_TUPLE))) return NULL;
359 break;
360 case 't': /* TUPLE */
361 if (pk_push(vm, pk_from_mark(vm, PK_TUPLE))) return NULL;
362 break;
363 case 'l': /* LIST */
364 if (pk_push(vm, pk_from_mark(vm, PK_LIST))) return NULL;
365 break;
366 case 0x85: /* TUPLE1 */
367 case 0x86: /* TUPLE2 */
368 case 0x87: { /* TUPLE3 */
369 size_t n = (size_t)(op - 0x84), k = 0;
370
371 if (vm->sp < n) return NULL;
372 a = pk_new(vm, PK_TUPLE);
373 if (!a) return NULL;
374 for (k = vm->sp - n; k < vm->sp; k++) {
375 if (vm->stack[k]->type == PK_MARK || pk_add(a, vm->stack[k])) return NULL;
376 }
377 vm->sp -= n;
378 if (pk_push(vm, a)) return NULL;
379 break;
380 }
381 case 'a': /* APPEND */
382 b = pk_pop(vm);
383 if (!b || !vm->sp || vm->stack[vm->sp - 1]->type != PK_LIST || pk_add(vm->stack[vm->sp - 1], b))
384 return NULL;
385 break;
386 case 'e': { /* APPENDS */
387 long m = pk_mark(vm);
388 size_t k = 0;
389
390 if (m < 1 || vm->stack[m - 1]->type != PK_LIST) return NULL;
391 for (k = (size_t)m + 1; k < vm->sp; k++)
392 if (pk_add(vm->stack[m - 1], vm->stack[k])) return NULL;
393 vm->sp = (size_t)m;
394 break;
395 }
396 case 'd': { /* DICT */
397 long m = pk_mark(vm);
398 size_t k = 0;
399
400 if (m < 0 || (vm->sp - (size_t)m - 1) % 2) return NULL;
401 a = pk_new(vm, PK_DICT);
402 if (!a) return NULL;
403 for (k = (size_t)m + 1; k < vm->sp; k++)
404 if (pk_add(a, vm->stack[k])) return NULL;
405 vm->sp = (size_t)m;
406 if (pk_push(vm, a)) return NULL;
407 break;
408 }
409 case 's': /* SETITEM */
410 b = pk_pop(vm);
411 a = pk_pop(vm);
412 if (!a || !b || !vm->sp || vm->stack[vm->sp - 1]->type != PK_DICT) return NULL;
413 if (pk_add(vm->stack[vm->sp - 1], a) || pk_add(vm->stack[vm->sp - 1], b)) return NULL;
414 break;
415 case 'u': { /* SETITEMS */
416 long m = pk_mark(vm);
417
418 if (m < 1 || (vm->sp - (size_t)m - 1) % 2) return NULL;
419 /* Items set on an opaque value, an object of a class this
420 * does not build, are dropped with it. */
421 if (vm->stack[m - 1]->type == PK_DICT) {
422 for (size_t k = (size_t)m + 1; k < vm->sp; k++)
423 if (pk_add(vm->stack[m - 1], vm->stack[k])) return NULL;
424 } else if (vm->stack[m - 1]->type != PK_OPAQUE) {
425 return NULL;
426 }
427 vm->sp = (size_t)m;
428 break;
429 }
430 case 'J': /* BININT */
431 if (LEFT < 4 || !(a = pk_new(vm, PK_INT))) return NULL;
432 a->i = (int32_t)(uint32_t)le(p + at, 4);
433 at += 4;
434 if (pk_push(vm, a)) return NULL;
435 break;
436 case 'K': /* BININT1 */
437 if (LEFT < 1 || !(a = pk_new(vm, PK_INT))) return NULL;
438 a->i = p[at++];
439 if (pk_push(vm, a)) return NULL;
440 break;
441 case 'M': /* BININT2 */
442 if (LEFT < 2 || !(a = pk_new(vm, PK_INT))) return NULL;
443 a->i = (int64_t)le(p + at, 2);
444 at += 2;
445 if (pk_push(vm, a)) return NULL;
446 break;
447 case 0x8a: { /* LONG1 */
448 size_t n = 0;
449
450 if (LEFT < 1) return NULL;
451 n = p[at++];
452 if (n > 8 || LEFT < n || !(a = pk_new(vm, PK_INT))) return NULL;
453 a->i = n ? (int64_t)le(p + at, (int)n) : 0;
454 /* Sign extend what was shorter than eight bytes. */
455 if (n && n < 8 && (p[at + n - 1] & 0x80)) a->i -= (int64_t)1 << (8 * n);
456 at += n;
457 if (pk_push(vm, a)) return NULL;
458 break;
459 }
460 case 'G': { /* BINFLOAT, big endian */
461 uint64_t bits = 0;
462 int k = 0;
463
464 if (LEFT < 8 || !(a = pk_new(vm, PK_FLOAT))) return NULL;
465 for (k = 0; k < 8; k++) bits = (bits << 8) | p[at + (size_t)k];
466 memcpy(&a->f, &bits, sizeof(a->f));
467 at += 8;
468 if (pk_push(vm, a)) return NULL;
469 break;
470 }
471 case 'N': /* NONE */
472 if (pk_push(vm, pk_new(vm, PK_NONE))) return NULL;
473 break;
474 case 0x88: /* NEWTRUE */
475 case 0x89: /* NEWFALSE */
476 if (!(a = pk_new(vm, PK_BOOL))) return NULL;
477 a->i = op == 0x88;
478 if (pk_push(vm, a)) return NULL;
479 break;
480 case 'X': /* BINUNICODE */
481 case 'T': /* BINSTRING */
482 case 'B': /* BINBYTES */
483 case 0x8c: /* SHORT_BINUNICODE */
484 case 'U': /* SHORT_BINSTRING */
485 case 'C': /* SHORT_BINBYTES */
486 case 0x8d: /* BINUNICODE8 */
487 case 0x8e: { /* BINBYTES8 */
488 int width = (op == 0x8c || op == 'U' || op == 'C') ? 1 : (op == 0x8d || op == 0x8e) ? 8
489 : 4;
490 uint64_t n = 0;
491
492 if (LEFT < (size_t)width) return NULL;
493 n = le(p + at, width);
494 at += (size_t)width;
495 if (n > LEFT) return NULL;
496 if (pk_push(vm, pk_string(vm, (op == 'B' || op == 'C' || op == 0x8e) ? PK_BYTES : PK_STR, p + at,
497 (size_t)n)))
498 return NULL;
499 at += (size_t)n;
500 break;
501 }
502 case 'c': { /* GLOBAL: "module\nname\n" */
503 const unsigned char* first = memchr(p + at, '\n', LEFT);
504 const unsigned char* second = first ? memchr(first + 1, '\n', (size_t)(p + len - first - 1)) : NULL;
505
506 if (!first || !second) return NULL;
507 a = pk_string(vm, PK_GLOBAL, p + at, (size_t)(second - (p + at)));
508 if (!a) return NULL;
509 a->s[first - (p + at)] = ' ';
510 at = (size_t)(second - p) + 1;
511 if (pk_push(vm, a)) return NULL;
512 break;
513 }
514 case 0x93: { /* STACK_GLOBAL */
515 size_t n = 0;
516
517 b = pk_pop(vm);
518 a = pk_pop(vm);
519 if (!a || !b || a->type != PK_STR || b->type != PK_STR) return NULL;
520 n = strlen(a->s) + strlen(b->s) + 2;
521 c = pk_new(vm, PK_GLOBAL);
522 if (!c || !(c->s = (char*)malloc(n))) return NULL;
523 snprintf(c->s, n, "%s %s", a->s, b->s);
524 if (pk_push(vm, c)) return NULL;
525 break;
526 }
527 case 'q': /* BINPUT */
528 if (LEFT < 1 || !vm->sp || pk_memo_put(vm, p[at], vm->stack[vm->sp - 1])) return NULL;
529 at += 1;
530 break;
531 case 'r': /* LONG_BINPUT */
532 if (LEFT < 4 || !vm->sp || pk_memo_put(vm, (size_t)le(p + at, 4), vm->stack[vm->sp - 1]))
533 return NULL;
534 at += 4;
535 break;
536 case 0x94: /* MEMOIZE */
537 if (!vm->sp) return NULL;
538 {
539 size_t k = 0;
540
541 /* The next free index: one past the highest in use. */
542 for (k = vm->memo_n; k > 0 && !vm->memo[k - 1]; k--) {
543 }
544 if (pk_memo_put(vm, k, vm->stack[vm->sp - 1])) return NULL;
545 }
546 break;
547 case 'h': /* BINGET */
548 case 'j': { /* LONG_BINGET */
549 size_t index = 0;
550
551 if (LEFT < (op == 'h' ? 1u : 4u)) return NULL;
552 index = op == 'h' ? p[at] : (size_t)le(p + at, 4);
553 at += op == 'h' ? 1 : 4;
554 if (index >= vm->memo_n || !vm->memo[index] || pk_push(vm, vm->memo[index])) return NULL;
555 break;
556 }
557 case 'Q': { /* BINPERSID: ("storage", type, key, location, numel) */
558 const char* dtype = NULL;
559
560 a = pk_pop(vm);
561 if (!a || a->type != PK_TUPLE || a->n < 5 || a->items[1]->type != PK_GLOBAL ||
562 a->items[2]->type != PK_STR || a->items[4]->type != PK_INT)
563 return NULL;
564 b = pk_new(vm, PK_STORAGE);
565 if (!b) return NULL;
566 b->element = storage_element(a->items[1]->s, &dtype);
567 b->dtype = dtype;
568 b->i = a->items[4]->i;
569 b->s = (char*)malloc(strlen(a->items[2]->s) + 1);
570 if (!b->s) return NULL;
571 memcpy(b->s, a->items[2]->s, strlen(a->items[2]->s) + 1);
572 if (pk_push(vm, b)) return NULL;
573 break;
574 }
575 case 'R': /* REDUCE */
576 case 0x81: /* NEWOBJ */
577 b = pk_pop(vm);
578 a = pk_pop(vm);
579 if (!a || !b || b->type != PK_TUPLE) return NULL;
580 if (pk_push(vm, pk_reduce(vm, a, b))) return NULL;
581 break;
582 case 'b': /* BUILD: state set on an object, not kept */
583 if (!pk_pop(vm) || !vm->sp) return NULL;
584 break;
585 default:
586 n_log(LOG_ERR, "pth: pickle opcode 0x%02x at %zu is not read", op, at - 1);
587 return NULL;
588 }
589 }
590#undef LEFT
591 return NULL;
592}
593
599static float element_value(const unsigned char* p, int element, const char* dtype) {
600 uint32_t bits = 0;
601 float f = 0.0f;
602
603 if (element == 4) {
604 bits = (uint32_t)le(p, 4);
605 } else if (element == 8) {
606 uint64_t b64 = le(p, 8);
607 double d = 0.0;
608
609 memcpy(&d, &b64, sizeof(d));
610 return (float)d;
611 } else if (strcmp(dtype, "bfloat16") == 0) {
612 bits = (uint32_t)le(p, 2) << 16;
613 } else {
614 /* IEEE half to single. */
615 uint32_t h = (uint32_t)le(p, 2), sign = (h & 0x8000u) << 16, exp = (h >> 10) & 0x1fu, man = h & 0x3ffu;
616
617 if (exp == 0) {
618 if (man == 0) {
619 bits = sign;
620 } else {
621 exp = 127 - 15 + 1;
622 while (!(man & 0x400u)) {
623 man <<= 1;
624 exp--;
625 }
626 bits = sign | (exp << 23) | ((man & 0x3ffu) << 13);
627 }
628 } else if (exp == 31) {
629 bits = sign | 0x7f800000u | (man << 13);
630 } else {
631 bits = sign | ((exp - 15 + 127) << 23) | (man << 13);
632 }
633 }
634 memcpy(&f, &bits, sizeof(f));
635 return f;
636}
637
644static int tensor_read(const char* path, const char* dir, const PK_OBJ* t, N_PTH_TENSOR* out) {
645 const PK_OBJ* storage = t->items[0];
646 const PK_OBJ* size = t->items[1];
647 const PK_OBJ* stride = t->items[2];
648 unsigned char* raw = NULL;
649 size_t raw_len = 0, i = 0, count = 1, span = 0;
650 char member[512];
651 int d = 0;
652
653 if (!storage->element) {
654 n_log(LOG_ERR, "pth: %s has storage of type %s, not read", out->name, storage->dtype);
655 return -1;
656 }
657 if (size->n > N_PTH_MAX_DIMS || size->n != stride->n || t->i < 0) return -1;
658 out->dims = (int)size->n;
659 for (d = 0; d < out->dims; d++) {
660 if (size->items[d]->type != PK_INT || stride->items[d]->type != PK_INT || size->items[d]->i < 0 ||
661 stride->items[d]->i < 0)
662 return -1;
663 out->shape[d] = size->items[d]->i;
664 if (out->shape[d] && count > (SIZE_MAX / sizeof(float)) / (size_t)out->shape[d]) return -1;
665 count *= (size_t)out->shape[d];
666 if (out->shape[d]) span += (size_t)(out->shape[d] - 1) * (size_t)stride->items[d]->i;
667 }
668 /* The last element read must be inside the storage. */
669 if (count && (size_t)t->i + span >= (size_t)storage->i) return -1;
670 if (snprintf(member, sizeof(member), "%sdata/%s", dir, storage->s) >= (int)sizeof(member)) return -1;
671 if (n_zip_read(path, member, PTH_STORAGE_MAX, &raw, &raw_len) != 0) return -1;
672 if (raw_len < (size_t)storage->i * (size_t)storage->element) {
673 free(raw);
674 return -1;
675 }
676 out->count = count;
677 out->data = (float*)malloc((count ? count : 1) * sizeof(float));
678 if (!out->data) {
679 free(raw);
680 return -1;
681 }
682 /* Row major, each index turned into the storage's position by the
683 * strides: a view is gathered, a contiguous tensor read in order. */
684 for (i = 0; i < count; i++) {
685 size_t rest = i, at = (size_t)t->i;
686
687 for (d = out->dims - 1; d >= 0; d--) {
688 size_t n = (size_t)out->shape[d];
689
690 at += (rest % n) * (size_t)stride->items[d]->i;
691 rest /= n;
692 }
693 out->data[i] = element_value(raw + at * (size_t)storage->element, storage->element, storage->dtype);
694 }
695 free(raw);
696 return 0;
697}
698
707static int collect(const char* path, const char* dir, const PK_OBJ* o, const char* prefix, N_PTH* pth, int depth) {
708 size_t k = 0;
709
710 if (depth > 32) return -1;
711 if (o->type == PK_TENSOR) {
712 N_PTH_TENSOR* grown = (N_PTH_TENSOR*)realloc(pth->tensors, (pth->count + 1) * sizeof(*grown));
713 N_PTH_TENSOR* t = NULL;
714
715 if (!grown) return -1;
716 pth->tensors = grown;
717 t = &pth->tensors[pth->count];
718 memset(t, 0, sizeof(*t));
719 t->name = (char*)malloc(strlen(prefix) + 1);
720 if (!t->name) return -1;
721 memcpy(t->name, prefix, strlen(prefix) + 1);
722 pth->count++;
723 return tensor_read(path, dir, o, t);
724 }
725 if (o->type != PK_DICT) return 0;
726 for (k = 0; k + 1 < o->n; k += 2) {
727 const PK_OBJ* key = o->items[k];
728 char name[1024];
729
730 if (key->type != PK_STR) continue;
731 if (snprintf(name, sizeof(name), "%s%s%s", prefix, prefix[0] ? "." : "", key->s) >= (int)sizeof(name))
732 return -1;
733 if (collect(path, dir, o->items[k + 1], name, pth, depth + 1) != 0) return -1;
734 }
735 return 0;
736}
737
738N_PTH* n_pth_load(const char* path) {
739 PK_VM vm;
740 N_PTH* pth = NULL;
741 unsigned char* pickle = NULL;
742 size_t pickle_len = 0, k = 0;
743 char member[512], dir[512];
744 const PK_OBJ* top = NULL;
745 const char* slash = NULL;
746
747 if (!path) return NULL;
748 if (n_zip_find_ending(path, "data.pkl", member, sizeof(member)) != 0) {
749 n_log(LOG_ERR, "pth: %s is not a checkpoint this reads (no data.pkl)", path);
750 return NULL;
751 }
752 /* The storages are under data/ beside data.pkl. */
753 slash = strrchr(member, '/');
754 snprintf(dir, sizeof(dir), "%.*s", slash ? (int)(slash - member + 1) : 0, member);
755 if (n_zip_read(path, member, PTH_PICKLE_MAX, &pickle, &pickle_len) != 0) return NULL;
756 memset(&vm, 0, sizeof(vm));
757 top = pk_run(&vm, pickle, pickle_len);
758 free(pickle);
759 if (top) {
760 pth = (N_PTH*)calloc(1, sizeof(*pth));
761 if (pth && collect(path, dir, top, "", pth, 0) != 0) n_pth_free(&pth);
762 } else {
763 n_log(LOG_ERR, "pth: the pickle in %s could not be read", path);
764 }
765 for (k = 0; k < vm.all_n; k++) {
766 free(vm.all[k]->s);
767 free((void*)vm.all[k]->items);
768 free(vm.all[k]);
769 }
770 free((void*)vm.all);
771 free((void*)vm.stack);
772 free((void*)vm.memo);
773 return pth;
774}
775
776const N_PTH_TENSOR* n_pth_find(const N_PTH* pth, const char* name) {
777 size_t k = 0;
778
779 for (k = 0; pth && name && k < pth->count; k++)
780 if (strcmp(pth->tensors[k].name, name) == 0) return &pth->tensors[k];
781 return NULL;
782}
783
784void n_pth_free(N_PTH** pth) {
785 size_t k = 0;
786
787 if (!pth || !*pth) return;
788 for (k = 0; k < (*pth)->count; k++) {
789 free((*pth)->tensors[k].name);
790 free((*pth)->tensors[k].data);
791 }
792 free((*pth)->tensors);
793 free(*pth);
794 *pth = NULL;
795}
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
volatile char key[227]
TRUE while the key is held.
void rest(unsigned int ms)
sleep ms milliseconds
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
size_t count
how many
Definition n_pth.h:76
int dims
how many dimensions
Definition n_pth.h:70
char * name
its path, the dictionary keys joined by dots; owned
Definition n_pth.h:68
N_PTH_TENSOR * tensors
the tensors, in the order the file lists them
Definition n_pth.h:82
int64_t shape[8]
their sizes
Definition n_pth.h:72
float * data
its values, contiguous, row major; owned
Definition n_pth.h:74
size_t count
how many
Definition n_pth.h:84
void n_pth_free(N_PTH **pth)
release a checkpoint's tensors
Definition n_pth.c:784
const N_PTH_TENSOR * n_pth_find(const N_PTH *pth, const char *name)
find a tensor by its path
Definition n_pth.c:776
N_PTH * n_pth_load(const char *path)
read every tensor of a checkpoint
Definition n_pth.c:738
#define N_PTH_MAX_DIMS
Most dimensions a tensor read may have.
Definition n_pth.h:63
A checkpoint's tensors.
Definition n_pth.h:80
One tensor of a checkpoint.
Definition n_pth.h:66
int n_zip_find_ending(const char *path, const char *ending, char *name, size_t name_len)
find the first member whose name ends with a given text, as a PyTorch checkpoint's "archive/data....
Definition n_zip.c:156
int n_zip_read(const char *path, const char *name, size_t max_size, unsigned char **data, size_t *len)
read a member into memory, checking its CRC-32
Definition n_zip.c:282
Generic log system.
int64_t i
an integer's value, a storage's element count, a tensor's offset
Definition n_pth.c:70
PK_OBJ ** all
every value made, freed together
Definition n_pth.c:91
static PK_OBJ * pk_string(PK_VM *vm, int type, const unsigned char *p, size_t len)
make a string value from bytes
Definition n_pth.c:197
const char * dtype
a storage's type name, for the log
Definition n_pth.c:78
static PK_OBJ * pk_run(PK_VM *vm, const unsigned char *p, size_t len)
run a pickle
Definition n_pth.c:317
static int pk_add(PK_OBJ *o, PK_OBJ *item)
add an item to a tuple, list or dictionary
Definition n_pth.c:135
static PK_OBJ * pk_pop(PK_VM *vm)
pop a value
Definition n_pth.c:173
#define PTH_PICKLE_MAX
Largest data.pkl read: a state dictionary's is a few kilobytes, a large model's a few hundred.
Definition n_pth.c:39
static PK_OBJ * pk_from_mark(PK_VM *vm, int type)
pop the values above the top mark into a new container
Definition n_pth.c:298
#define LEFT
static PK_OBJ * pk_reduce(PK_VM *vm, const PK_OBJ *callable, PK_OBJ *args)
the call a REDUCE or NEWOBJ makes, done as data
Definition n_pth.c:272
PK_OBJ ** stack
the stack
Definition n_pth.c:97
#define PTH_STORAGE_MAX
Largest single storage read.
Definition n_pth.c:41
static int collect(const char *path, const char *dir, const PK_OBJ *o, const char *prefix, N_PTH *pth, int depth)
collect the tensors under a dictionary, depth first
Definition n_pth.c:707
static int pk_push(PK_VM *vm, PK_OBJ *o)
push a value
Definition n_pth.c:154
char * s
a string, a global's "module name", a storage's key; owned
Definition n_pth.c:74
static int storage_element(const char *name, const char **dtype)
a storage type's element size, from its global's name
Definition n_pth.c:245
PK_TYPE
What a pickle value is.
Definition n_pth.c:47
@ PK_BOOL
Definition n_pth.c:49
@ PK_TENSOR
Definition n_pth.c:59
@ PK_MARK
Definition n_pth.c:61
@ PK_TUPLE
Definition n_pth.c:54
@ PK_OPAQUE
Definition n_pth.c:60
@ PK_BYTES
Definition n_pth.c:53
@ PK_LIST
Definition n_pth.c:55
@ PK_INT
Definition n_pth.c:50
@ PK_STR
Definition n_pth.c:52
@ PK_STORAGE
Definition n_pth.c:58
@ PK_GLOBAL
Definition n_pth.c:57
@ PK_FLOAT
Definition n_pth.c:51
@ PK_DICT
Definition n_pth.c:56
@ PK_NONE
Definition n_pth.c:48
struct PK_OBJ ** items
items of a tuple, list or dictionary; a tensor's storage, then its size and stride tuples
Definition n_pth.c:81
size_t cap
room for
Definition n_pth.c:85
static float element_value(const unsigned char *p, int element, const char *dtype)
convert one stored element to a float
Definition n_pth.c:599
#define PTH_OBJECTS_MAX
Most values the interpreter makes, and most memo entries: a guard, far above any real checkpoint's.
Definition n_pth.c:44
double f
a float's value
Definition n_pth.c:72
static int tensor_read(const char *path, const char *dir, const PK_OBJ *t, N_PTH_TENSOR *out)
read a tensor's values out of its storage file
Definition n_pth.c:644
size_t n
how many
Definition n_pth.c:83
size_t stack_cap
room for
Definition n_pth.c:101
static PK_OBJ * pk_new(PK_VM *vm, int type)
make a value, owned by the interpreter
Definition n_pth.c:112
static long pk_mark(const PK_VM *vm)
where the top mark is on the stack
Definition n_pth.c:181
size_t memo_n
its size
Definition n_pth.c:105
size_t all_cap
room for
Definition n_pth.c:95
static uint64_t le(const unsigned char *p, int n)
read a little endian unsigned value
Definition n_pth.c:233
int element
a storage's element size in bytes
Definition n_pth.c:76
static int pk_memo_put(PK_VM *vm, size_t index, PK_OBJ *o)
store a value in the memo
Definition n_pth.c:214
size_t all_n
how many
Definition n_pth.c:93
int type
a PK_TYPE
Definition n_pth.c:68
size_t sp
its depth
Definition n_pth.c:99
PK_OBJ ** memo
the memo
Definition n_pth.c:103
One pickle value.
Definition n_pth.c:66
The interpreter.
Definition n_pth.c:89
Reading the tensors of a PyTorch checkpoint (.pth, .pt) without Python.
Reading members out of a ZIP archive, to a file or into memory.