Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_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
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include <zlib.h>
36
37#include "nilorea/n_common.h"
38#include "nilorea/n_log.h"
39#include "nilorea/n_pth.h"
40
42static int log_level = LOG_ERR;
44static int failures = 0;
45
50static const unsigned char checkpoint_pickle[] = {
51 0x80, 0x02, 0x7d, 0x71, 0x00, 0x28, 0x58, 0x04, 0x00, 0x00, 0x00, 0x6d,
52 0x65, 0x74, 0x61, 0x71, 0x01, 0x7d, 0x71, 0x02, 0x28, 0x58, 0x05, 0x00,
53 0x00, 0x00, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x71, 0x03, 0x4b, 0x0c, 0x58,
54 0x04, 0x00, 0x00, 0x00, 0x6e, 0x6f, 0x74, 0x65, 0x71, 0x04, 0x58, 0x01,
55 0x00, 0x00, 0x00, 0x78, 0x71, 0x05, 0x58, 0x02, 0x00, 0x00, 0x00, 0x6c,
56 0x72, 0x71, 0x06, 0x47, 0x3f, 0x50, 0x62, 0x4d, 0xd2, 0xf1, 0xa9, 0xfc,
57 0x75, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
58 0x64, 0x69, 0x63, 0x74, 0x71, 0x07, 0x63, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
59 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x4f, 0x72, 0x64, 0x65, 0x72,
60 0x65, 0x64, 0x44, 0x69, 0x63, 0x74, 0x0a, 0x71, 0x08, 0x29, 0x52, 0x71,
61 0x09, 0x28, 0x58, 0x0c, 0x00, 0x00, 0x00, 0x6c, 0x61, 0x79, 0x65, 0x72,
62 0x2e, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x71, 0x0a, 0x63, 0x74, 0x6f,
63 0x72, 0x63, 0x68, 0x2e, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x73, 0x0a, 0x5f,
64 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x65, 0x6e, 0x73,
65 0x6f, 0x72, 0x5f, 0x76, 0x32, 0x0a, 0x71, 0x0b, 0x28, 0x28, 0x58, 0x07,
66 0x00, 0x00, 0x00, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x71, 0x0c,
67 0x63, 0x74, 0x6f, 0x72, 0x63, 0x68, 0x0a, 0x46, 0x6c, 0x6f, 0x61, 0x74,
68 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0a, 0x71, 0x0d, 0x58, 0x01,
69 0x00, 0x00, 0x00, 0x30, 0x71, 0x0e, 0x58, 0x03, 0x00, 0x00, 0x00, 0x63,
70 0x70, 0x75, 0x71, 0x0f, 0x4b, 0x06, 0x74, 0x71, 0x10, 0x51, 0x4b, 0x00,
71 0x4b, 0x02, 0x4b, 0x03, 0x86, 0x71, 0x11, 0x4b, 0x03, 0x4b, 0x01, 0x86,
72 0x71, 0x12, 0x89, 0x68, 0x08, 0x29, 0x52, 0x71, 0x13, 0x74, 0x71, 0x14,
73 0x52, 0x71, 0x15, 0x58, 0x0e, 0x00, 0x00, 0x00, 0x6c, 0x61, 0x79, 0x65,
74 0x72, 0x2e, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x74, 0x71, 0x16,
75 0x68, 0x0b, 0x28, 0x68, 0x10, 0x51, 0x4b, 0x00, 0x4b, 0x03, 0x4b, 0x02,
76 0x86, 0x71, 0x17, 0x4b, 0x01, 0x4b, 0x03, 0x86, 0x71, 0x18, 0x89, 0x68,
77 0x08, 0x29, 0x52, 0x71, 0x19, 0x74, 0x71, 0x1a, 0x52, 0x71, 0x1b, 0x58,
78 0x0a, 0x00, 0x00, 0x00, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x62, 0x69,
79 0x61, 0x73, 0x71, 0x1c, 0x68, 0x0b, 0x28, 0x68, 0x10, 0x51, 0x4b, 0x04,
80 0x4b, 0x02, 0x85, 0x71, 0x1d, 0x4b, 0x01, 0x85, 0x71, 0x1e, 0x89, 0x68,
81 0x08, 0x29, 0x52, 0x71, 0x1f, 0x74, 0x71, 0x20, 0x52, 0x71, 0x21, 0x58,
82 0x04, 0x00, 0x00, 0x00, 0x68, 0x61, 0x6c, 0x66, 0x71, 0x22, 0x68, 0x0b,
83 0x28, 0x28, 0x68, 0x0c, 0x63, 0x74, 0x6f, 0x72, 0x63, 0x68, 0x0a, 0x48,
84 0x61, 0x6c, 0x66, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0a, 0x71,
85 0x23, 0x58, 0x01, 0x00, 0x00, 0x00, 0x31, 0x71, 0x24, 0x68, 0x0f, 0x4b,
86 0x04, 0x74, 0x71, 0x25, 0x51, 0x4b, 0x00, 0x4b, 0x04, 0x85, 0x71, 0x26,
87 0x68, 0x1e, 0x89, 0x68, 0x08, 0x29, 0x52, 0x71, 0x27, 0x74, 0x71, 0x28,
88 0x52, 0x71, 0x29, 0x75, 0x75, 0x2e};
89
93static void expect_true(const char* what, int ok) {
94 if (!ok) {
95 n_log(LOG_ERR, "FAIL: %s", what);
96 failures++;
97 } else {
98 n_log(LOG_INFO, "ok: %s", what);
99 }
100}
101
106static int process_args(int argc, char** argv) {
107 int opt = 0;
108
109 while ((opt = getopt(argc, argv, "V:")) != -1) {
110 if (opt == 'V') {
111 log_level = atoi(optarg);
112 } else {
113 return -1;
114 }
115 }
116 return 0;
117}
118
123static void put_le(unsigned char* p, unsigned long v, int n) {
124 int i = 0;
125
126 for (i = 0; i < n; i++) p[i] = (unsigned char)((v >> (8 * i)) & 0xff);
127}
128
130typedef struct {
132 const char* name;
134 const unsigned char* data;
136 size_t len;
137} MEMBER;
138
144static int write_zip(const char* path, const MEMBER* m, int count) {
145 FILE* f = fopen(path, "wb");
146 unsigned long offsets[8], crcs[8], at = 0, dir_size = 0;
147 unsigned char h[46];
148 int i = 0;
149
150 if (!f || count > 8) {
151 if (f) fclose(f);
152 return -1;
153 }
154 for (i = 0; i < count; i++) {
155 size_t n = strlen(m[i].name);
156
157 crcs[i] = crc32(crc32(0L, Z_NULL, 0), m[i].data, (uInt)m[i].len);
158 memset(h, 0, sizeof(h));
159 put_le(h, 0x04034b50ul, 4);
160 put_le(h + 4, 20, 2);
161 put_le(h + 14, crcs[i], 4);
162 put_le(h + 18, (unsigned long)m[i].len, 4);
163 put_le(h + 22, (unsigned long)m[i].len, 4);
164 put_le(h + 26, (unsigned long)n, 2);
165 offsets[i] = at;
166 fwrite(h, 1, 30, f);
167 fwrite(m[i].name, 1, n, f);
168 fwrite(m[i].data, 1, m[i].len, f);
169 at += 30 + (unsigned long)n + (unsigned long)m[i].len;
170 }
171 for (i = 0; i < count; i++) {
172 size_t n = strlen(m[i].name);
173
174 memset(h, 0, sizeof(h));
175 put_le(h, 0x02014b50ul, 4);
176 put_le(h + 4, 20, 2);
177 put_le(h + 6, 20, 2);
178 put_le(h + 16, crcs[i], 4);
179 put_le(h + 20, (unsigned long)m[i].len, 4);
180 put_le(h + 24, (unsigned long)m[i].len, 4);
181 put_le(h + 28, (unsigned long)n, 2);
182 put_le(h + 42, offsets[i], 4);
183 fwrite(h, 1, 46, f);
184 fwrite(m[i].name, 1, n, f);
185 dir_size += 46 + (unsigned long)n;
186 }
187 memset(h, 0, 22);
188 put_le(h, 0x06054b50ul, 4);
189 put_le(h + 8, (unsigned long)count, 2);
190 put_le(h + 10, (unsigned long)count, 2);
191 put_le(h + 12, dir_size, 4);
192 put_le(h + 16, at, 4);
193 fwrite(h, 1, 22, f);
194 return fclose(f) == 0 ? 0 : -1;
195}
196
202static int values_are(const N_PTH_TENSOR* t, const float* want, size_t n) {
203 size_t i = 0;
204
205 if (!t || t->count != n) return 0;
206 for (i = 0; i < n; i++)
207 if (t->data[i] != want[i]) return 0;
208 return 1;
209}
210
215int main(int argc, char** argv) {
216 static const char* path = "ex_pth.tmp.pth";
217 static const float f32[6] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f};
218 /* 0.5, -1, 2 and 65504, the largest float16, little endian halves. */
219 static const unsigned char f16[8] = {0x00, 0x38, 0x00, 0xbc, 0x00, 0x40, 0xff, 0x7b};
220 unsigned char f32_bytes[24];
221 MEMBER members[3];
222 N_PTH* pth = NULL;
223 int i = 0;
224
226 if (process_args(argc, argv) != 0) {
227 fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
228 return 2;
229 }
231 /* The storage as torch writes it: raw little endian float32. */
232 for (i = 0; i < 6; i++) {
233 uint32_t bits = 0;
234
235 memcpy(&bits, &f32[i], 4);
236 put_le(f32_bytes + 4 * i, bits, 4);
237 }
238 members[0].name = "archive/data.pkl";
239 members[0].data = checkpoint_pickle;
240 members[0].len = sizeof(checkpoint_pickle);
241 members[1].name = "archive/data/0";
242 members[1].data = f32_bytes;
243 members[1].len = sizeof(f32_bytes);
244 members[2].name = "archive/data/1";
245 members[2].data = f16;
246 members[2].len = sizeof(f16);
247 expect_true("a checkpoint is written", write_zip(path, members, 3) == 0);
248
249 pth = n_pth_load(path);
250 expect_true("it is read", pth != NULL);
251 if (pth) {
252 const N_PTH_TENSOR* w = n_pth_find(pth, "state_dict.layer.weight");
253 const float transposed[6] = {1.0f, 4.0f, 2.0f, 5.0f, 3.0f, 6.0f};
254 const float bias[2] = {5.0f, 6.0f};
255 const float halves[4] = {0.5f, -1.0f, 2.0f, 65504.0f};
256
257 expect_true("four tensors, the metadata left out", pth->count == 4);
258 expect_true("a tensor has its path, shape and values",
259 w && w->dims == 2 && w->shape[0] == 2 && w->shape[1] == 3 && values_are(w, f32, 6));
260 expect_true("a transposed view is gathered",
261 values_are(n_pth_find(pth, "state_dict.layer.weight_t"), transposed, 6));
262 expect_true("an offset view starts where it says",
263 values_are(n_pth_find(pth, "state_dict.layer.bias"), bias, 2));
264 expect_true("float16 is converted", values_are(n_pth_find(pth, "state_dict.half"), halves, 4));
265 expect_true("a missing name is not found", n_pth_find(pth, "state_dict.nothing") == NULL);
266 n_pth_free(&pth);
267 expect_true("a freed checkpoint is cleared", pth == NULL);
268 }
269
270 /* A storage missing: refused, not read past. */
271 expect_true("a checkpoint missing a storage is written", write_zip(path, members, 2) == 0);
272 pth = n_pth_load(path);
273 expect_true("and is refused", pth == NULL);
274 n_pth_free(&pth);
275 /* A pickle cut short. */
276 members[0].len = sizeof(checkpoint_pickle) / 2;
277 expect_true("a cut pickle is written", write_zip(path, members, 3) == 0);
278 pth = n_pth_load(path);
279 expect_true("and is refused", pth == NULL);
280 n_pth_free(&pth);
281 /* A storage shorter than the tensor that reads it. */
282 members[0].len = sizeof(checkpoint_pickle);
283 members[1].len = 8;
284 expect_true("a short storage is written", write_zip(path, members, 3) == 0);
285 pth = n_pth_load(path);
286 expect_true("and is refused", pth == NULL);
287 n_pth_free(&pth);
288 expect_true("a file that is not a checkpoint is refused", n_pth_load("ex_pth.c") == NULL);
289 remove(path);
290 if (failures) {
291 fprintf(stderr, "ex_pth: %d failure(s)\n", failures);
292 return 1;
293 }
294 printf("ex_pth: ok\n");
295 return 0;
296}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static int log_level
static const unsigned char checkpoint_pickle[]
What torch.save() writes as data.pkl for {"meta": {...}, "state_dict": OrderedDict(layer....
Definition ex_pth.c:50
static void put_le(unsigned char *p, unsigned long v, int n)
put a little endian value
Definition ex_pth.c:123
static int write_zip(const char *path, const MEMBER *m, int count)
write a ZIP of stored members, as torch.save() does
Definition ex_pth.c:144
static int values_are(const N_PTH_TENSOR *t, const float *want, size_t n)
are a tensor's values these
Definition ex_pth.c:202
const char * name
its name
Definition ex_pth.c:132
size_t len
how many
Definition ex_pth.c:136
const unsigned char * data
its bytes
Definition ex_pth.c:134
one stored member
Definition ex_pth.c:130
#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
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_INFO
informational
Definition n_log.h:106
size_t count
how many
Definition n_pth.h:76
int dims
how many dimensions
Definition n_pth.h:70
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
A checkpoint's tensors.
Definition n_pth.h:80
One tensor of a checkpoint.
Definition n_pth.h:66
Common headers and low-level functions & define.
Generic log system.
Reading the tensors of a PyTorch checkpoint (.pth, .pt) without Python.