GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 87.7% 477 / 0 / 544
Functions: 91.7% 33 / 0 / 36
Branches: 57.9% 505 / 0 / 872

src/safetensors.cpp
Line Branch Exec Source
1 #include "brotensor/safetensors.h"
2
3 #include "brotensor/ops/elementwise.h"
4 #include "brotensor/runtime.h"
5 #include "brotensor/tensor.h"
6
7 #include <cstdint>
8 #include <cstdio>
9 #include <cstring>
10 #include <stdexcept>
11 #include <string>
12 #include <unordered_set>
13 #include <utility>
14 #include <vector>
15
16 #ifdef _WIN32
17 #define WIN32_LEAN_AND_MEAN
18 #define NOMINMAX
19 #include <windows.h>
20 #else
21 #include <fcntl.h>
22 #include <sys/mman.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #endif
26
27 namespace brotensor::safetensors {
28
29 // ─── Dtype helpers ─────────────────────────────────────────────────────────
30
31 13 const char* dtype_name(Dtype d) {
32
9/9
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 1 time.
13 switch (d) {
33 1 case Dtype::F32: return "F32";
34 1 case Dtype::F16: return "F16";
35 2 case Dtype::BF16: return "BF16";
36 2 case Dtype::I32: return "I32";
37 2 case Dtype::I64: return "I64";
38 2 case Dtype::U8: return "U8";
39 1 case Dtype::BOOL: return "BOOL";
40 1 case Dtype::Unknown:
41 2 default: return "Unknown";
42 }
43 13 }
44
45 90 int dtype_size_bytes(Dtype d) {
46
8/8
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 20 times.
✓ Branch 7 taken 3 times.
90 switch (d) {
47 29 case Dtype::F32: return 4;
48 12 case Dtype::F16: return 2;
49 15 case Dtype::BF16: return 2;
50 4 case Dtype::I32: return 4;
51 3 case Dtype::I64: return 8;
52 20 case Dtype::U8: return 1;
53 3 case Dtype::BOOL: return 1;
54 4 default: return 0;
55 }
56 90 }
57
58 32 static Dtype parse_dtype(std::string_view s) {
59
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 18 times.
32 if (s == "F32") return Dtype::F32;
60
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
18 if (s == "F16") return Dtype::F16;
61
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
16 if (s == "BF16") return Dtype::BF16;
62
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 if (s == "I32") return Dtype::I32;
63
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 11 times.
12 if (s == "I64") return Dtype::I64;
64
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 if (s == "U8") return Dtype::U8;
65
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if (s == "BOOL") return Dtype::BOOL;
66 1 return Dtype::Unknown;
67 32 }
68
69 43 int64_t TensorView::numel() const {
70 43 int64_t n = 1;
71
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 40 times.
94 for (int64_t d : shape) {
72
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 2 times.
54 if (d < 0)
73
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 throw std::runtime_error("safetensors: negative dimension in tensor shape");
74
4/4
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 1 time.
52 if (d != 0 && n > INT64_MAX / d)
75
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 throw std::runtime_error("safetensors: tensor element count overflows int64");
76 51 n *= d;
77 }
78 40 return n;
79 }
80
81 // ─── Minimal JSON parser ───────────────────────────────────────────────────
82 //
83 // Safetensors headers use a strict subset of JSON: top-level object whose
84 // values are either an object {dtype, shape, data_offsets} or the
85 // __metadata__ string->string object. We parse exactly that and reject
86 // anything that doesn't match. No nested escapes beyond \\, \", \n, \t are
87 // expected in tensor names; we handle them defensively anyway.
88
89 namespace {
90
91 struct Parser {
92 const char* p;
93 const char* end;
94
95 8 [[noreturn]] void fail(const std::string& msg) const {
96
4/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
8 throw std::runtime_error("safetensors: parse error: " + msg);
97 8 }
98
99 1145 void skip_ws() {
100
9/10
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1164 times.
✓ Branch 2 taken 1145 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 1145 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1144 times.
✓ Branch 8 taken 20 times.
✓ Branch 9 taken 1145 times.
2310 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) ++p;
101 1145 }
102
103 629 bool eat(char c) {
104 629 skip_ws();
105
4/4
✓ Branch 0 taken 628 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 172 times.
✓ Branch 3 taken 456 times.
629 if (p < end && *p == c) { ++p; return true; }
106 173 return false;
107 629 }
108 392 void expect(char c) {
109
5/8
✓ Branch 0 taken 389 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
392 if (!eat(c)) fail(std::string("expected '") + c + "'");
110 392 }
111
112 // Four hex digits at q -> value, or -1 if any is not a hex digit.
113 static int hex4(const char* q) {
114 int v = 0;
115 for (int i = 0; i < 4; ++i) {
116 const char c = q[i];
117 int d;
118 if (c >= '0' && c <= '9') d = c - '0';
119 else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
120 else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
121 else return -1;
122 v = (v << 4) | d;
123 }
124 return v;
125 }
126
127 static void append_utf8(std::string& out, uint32_t cp) {
128 if (cp < 0x80) {
129 out += static_cast<char>(cp);
130 } else if (cp < 0x800) {
131 out += static_cast<char>(0xC0 | (cp >> 6));
132 out += static_cast<char>(0x80 | (cp & 0x3F));
133 } else if (cp < 0x10000) {
134 out += static_cast<char>(0xE0 | (cp >> 12));
135 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
136 out += static_cast<char>(0x80 | (cp & 0x3F));
137 } else {
138 out += static_cast<char>(0xF0 | (cp >> 18));
139 out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
140 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
141 out += static_cast<char>(0x80 | (cp & 0x3F));
142 }
143 }
144
145 180 std::string parse_string() {
146 180 skip_ws();
147
3/4
✓ Branch 0 taken 179 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
182 if (p >= end || *p != '"') fail("expected string");
148 179 ++p;
149 179 std::string out;
150
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1169 times.
✓ Branch 2 taken 992 times.
✓ Branch 3 taken 178 times.
1170 while (p < end && *p != '"') {
151
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 975 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
992 if (*p == '\\' && p + 1 < end) {
152 17 char e = p[1];
153 // \uXXXX carries a UTF-16 code unit, possibly the high half of
154 // a surrogate pair. It must be decoded and re-emitted as UTF-8:
155 // safetensors headers are UTF-8 JSON, \u is what most writers
156 // emit for a non-ASCII tensor name, and it is what our own
157 // json_escape_append emits for control characters — so without
158 // this we reject both third-party files and our own output.
159
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (e == 'u') {
160 if (p + 6 > end) fail("truncated \\u escape in string");
161 int hi = hex4(p + 2);
162 if (hi < 0) fail("bad hex digit in \\u escape");
163 p += 6;
164 uint32_t cp = static_cast<uint32_t>(hi);
165 if (hi >= 0xD800 && hi <= 0xDBFF) {
166 if (p + 6 > end || p[0] != '\\' || p[1] != 'u')
167 fail("unpaired high surrogate in string");
168 const int lo = hex4(p + 2);
169 if (lo < 0) fail("bad hex digit in \\u escape");
170 if (lo < 0xDC00 || lo > 0xDFFF)
171 fail("unpaired high surrogate in string");
172 cp = 0x10000u + ((static_cast<uint32_t>(hi) - 0xD800u) << 10)
173 + (static_cast<uint32_t>(lo) - 0xDC00u);
174 p += 6;
175 } else if (hi >= 0xDC00 && hi <= 0xDFFF) {
176 fail("unpaired low surrogate in string");
177 }
178 append_utf8(out, cp);
179 continue; // p is already past the escape
180 }
181
9/9
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 1 time.
17 switch (e) {
182
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case '"': out += '"'; break;
183
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case '\\': out += '\\'; break;
184
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 case '/': out += '/'; break;
185
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 'n': out += '\n'; break;
186
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 't': out += '\t'; break;
187
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 'r': out += '\r'; break;
188
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 'b': out += '\b'; break;
189
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 'f': out += '\f'; break;
190
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 default: fail("unsupported escape in string");
191 }
192 16 p += 2;
193 16 } else {
194
1/2
✓ Branch 0 taken 975 times.
✗ Branch 1 not taken.
975 out += *p++;
195 }
196 }
197
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 177 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
178 if (p >= end) fail("unterminated string");
198 177 ++p;
199 177 return out;
200 180 }
201
202 91 int64_t parse_int() {
203 91 skip_ws();
204 91 bool neg = false;
205
3/4
✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 90 times.
✓ Branch 3 taken 1 time.
91 if (p < end && *p == '-') { neg = true; ++p; }
206
3/4
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
92 if (p >= end || *p < '0' || *p > '9') fail("expected integer");
207 90 int64_t v = 0;
208
5/6
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 189 times.
✓ Branch 4 taken 132 times.
✓ Branch 5 taken 89 times.
221 while (p < end && *p >= '0' && *p <= '9') {
209 132 const int digit = *p - '0';
210
3/4
✓ Branch 0 taken 131 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
132 if (v > (INT64_MAX - digit) / 10) fail("integer out of range");
211 131 v = v * 10 + digit;
212 131 ++p;
213 }
214
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 88 times.
89 return neg ? -v : v;
215 2 }
216
217 // Skip an arbitrary JSON value (used for __metadata__ entries we don't
218 // care about, and as a defensive fallback).
219 8 void skip_value() {
220 8 skip_ws();
221
1/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
8 if (p >= end) fail("unexpected EOF");
222
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 7 times.
8 if (*p == '"') { (void)parse_string(); return; }
223
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 3 times.
7 if (*p == '{' || *p == '[') {
224 4 char open = *p++, close = (open == '{') ? '}' : ']';
225 4 int depth = 1;
226
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 4 times.
44 while (p < end && depth > 0) {
227
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 29 times.
40 if (*p == '"') { (void)parse_string(); continue; }
228
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 28 times.
29 if (*p == open) ++depth;
229
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 5 times.
28 else if (*p == close) --depth;
230 29 ++p;
231 }
232 4 return;
233 }
234 // number / true / false / null
235
7/10
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 9 times.
15 while (p < end && *p != ',' && *p != '}' && *p != ']' &&
236
3/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 9 times.
18 *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') ++p;
237 8 }
238 };
239
240 64 struct Entry {
241 32 Dtype dtype = Dtype::Unknown;
242 std::vector<int64_t> shape;
243 32 uint64_t off_start = 0;
244 32 uint64_t off_end = 0;
245 };
246
247 32 Entry parse_entry(Parser& ps) {
248 32 Entry e;
249
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 ps.expect('{');
250 32 bool first = true;
251 126 while (true) {
252 126 ps.skip_ws();
253
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 28 times.
126 if (ps.eat('}')) break;
254
3/4
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
98 if (!first) ps.expect(',');
255 98 first = false;
256
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 std::string key = ps.parse_string();
257
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 ps.expect(':');
258
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 66 times.
98 if (key == "dtype") {
259
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 std::string v = ps.parse_string();
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 e.dtype = parse_dtype(v);
261
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1 time.
32 if (e.dtype == Dtype::Unknown) {
262
5/10
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
1 throw std::runtime_error("safetensors: unsupported dtype '" + v + "'");
263 }
264
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 35 times.
98 } else if (key == "shape") {
265
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 ps.expect('[');
266 31 bool sfirst = true;
267 63 while (true) {
268 63 ps.skip_ws();
269
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 34 times.
63 if (ps.eat(']')) break;
270
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
34 if (!sfirst) ps.expect(',');
271 34 sfirst = false;
272
3/4
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
34 e.shape.push_back(ps.parse_int());
273 }
274
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 6 times.
64 } else if (key == "data_offsets") {
275
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 ps.expect('[');
276
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 e.off_start = static_cast<uint64_t>(ps.parse_int());
277
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 1 time.
29 ps.expect(',');
278
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 e.off_end = static_cast<uint64_t>(ps.parse_int());
279
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 ps.expect(']');
280 28 } else {
281 // Unknown field — skip its value defensively.
282
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 ps.skip_value();
283 }
284 98 }
285 28 return e;
286 36 }
287
288 } // namespace
289
290 // ─── mmap (platform) ───────────────────────────────────────────────────────
291
292 namespace {
293
294 27 struct Mapping {
295 27 void* file = nullptr;
296 27 void* mapping = nullptr;
297 27 void* base = nullptr;
298 27 std::size_t size = 0;
299 };
300
301 27 Mapping map_file(const std::string& path) {
302 27 Mapping m;
303 #ifdef _WIN32
304 HANDLE fh = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
305 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
306 if (fh == INVALID_HANDLE_VALUE) {
307 throw std::runtime_error("safetensors: cannot open '" + path + "'");
308 }
309 LARGE_INTEGER sz;
310 if (!GetFileSizeEx(fh, &sz)) {
311 CloseHandle(fh);
312 throw std::runtime_error("safetensors: GetFileSizeEx failed for '" + path + "'");
313 }
314 HANDLE mh = CreateFileMappingA(fh, nullptr, PAGE_READONLY, 0, 0, nullptr);
315 if (!mh) {
316 CloseHandle(fh);
317 throw std::runtime_error("safetensors: CreateFileMapping failed for '" + path + "'");
318 }
319 void* base = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, 0);
320 if (!base) {
321 CloseHandle(mh);
322 CloseHandle(fh);
323 throw std::runtime_error("safetensors: MapViewOfFile failed for '" + path + "'");
324 }
325 m.file = fh;
326 m.mapping = mh;
327 m.base = base;
328 m.size = static_cast<std::size_t>(sz.QuadPart);
329 #else
330 27 int fd = ::open(path.c_str(), O_RDONLY);
331
6/10
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
27 if (fd < 0) throw std::runtime_error("safetensors: cannot open '" + path + "'");
332 26 struct stat st{};
333
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (fstat(fd, &st) != 0) {
334 ::close(fd);
335 throw std::runtime_error("safetensors: fstat failed for '" + path + "'");
336 }
337 26 void* base = ::mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
338
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (base == MAP_FAILED) {
339 ::close(fd);
340 throw std::runtime_error("safetensors: mmap failed for '" + path + "'");
341 }
342 26 m.file = reinterpret_cast<void*>(static_cast<intptr_t>(fd));
343 26 m.mapping = nullptr;
344 26 m.base = base;
345 26 m.size = static_cast<std::size_t>(st.st_size);
346 #endif
347 26 return m;
348 1 }
349
350 30 void unmap_file(Mapping& m) noexcept {
351 #ifdef _WIN32
352 if (m.base) UnmapViewOfFile(m.base);
353 if (m.mapping) CloseHandle(m.mapping);
354 if (m.file) CloseHandle(m.file);
355 #else
356
4/6
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
30 if (m.base && m.size) ::munmap(m.base, m.size);
357
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
30 if (m.file) ::close(static_cast<int>(reinterpret_cast<intptr_t>(m.file)));
358 #endif
359 30 m = Mapping{};
360 30 }
361
362 } // namespace
363
364 // ─── File ──────────────────────────────────────────────────────────────────
365
366 50 File::~File() { release_(); }
367
368 2 File::File(File&& other) noexcept
369 1 : file_(other.file_), mapping_(other.mapping_), base_(other.base_),
370 1 file_size_(other.file_size_),
371 2 tensors_(std::move(other.tensors_)), index_(std::move(other.index_)) {
372 1 other.file_ = other.mapping_ = other.base_ = nullptr;
373 1 other.file_size_ = 0;
374 2 }
375
376 2 File& File::operator=(File&& other) noexcept {
377
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (this != &other) {
378 2 release_();
379 2 file_ = other.file_;
380 2 mapping_ = other.mapping_;
381 2 base_ = other.base_;
382 2 file_size_ = other.file_size_;
383 2 tensors_ = std::move(other.tensors_);
384 2 index_ = std::move(other.index_);
385 2 other.file_ = other.mapping_ = other.base_ = nullptr;
386 2 other.file_size_ = 0;
387 2 }
388 2 return *this;
389 }
390
391 27 void File::release_() noexcept {
392 27 Mapping m{file_, mapping_, base_, file_size_};
393 27 unmap_file(m);
394 27 file_ = mapping_ = base_ = nullptr;
395 27 file_size_ = 0;
396 27 tensors_.clear();
397 27 index_.clear();
398 27 }
399
400 26 File File::open(const std::string& path) {
401 26 Mapping m = map_file(path);
402
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1 time.
26 if (m.size < 8) {
403 1 unmap_file(m);
404
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
14 throw std::runtime_error("safetensors: file too small: '" + path + "'");
405 }
406 25 const uint8_t* bytes = static_cast<const uint8_t*>(m.base);
407 25 uint64_t header_size = 0;
408 25 std::memcpy(&header_size, bytes, 8); // little-endian; platforms we target are LE
409
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 2 times.
25 if (header_size > m.size - 8) {
410 2 unmap_file(m);
411
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 throw std::runtime_error("safetensors: header_size exceeds file");
412 }
413 23 const char* hdr_begin = reinterpret_cast<const char*>(bytes + 8);
414 23 const char* hdr_end = hdr_begin + header_size;
415 23 const uint8_t* payload_base = bytes + 8 + header_size;
416 23 std::size_t payload_size = m.size - 8 - header_size;
417
418 23 File f;
419 23 f.file_ = m.file;
420 23 f.mapping_ = m.mapping;
421 23 f.base_ = m.base;
422 23 f.file_size_ = m.size;
423
424 23 Parser ps{hdr_begin, hdr_end};
425 try {
426
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1 time.
23 ps.expect('{');
427 22 bool first = true;
428 48 while (true) {
429
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 ps.skip_ws();
430
3/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✓ Branch 3 taken 10 times.
48 if (ps.eat('}')) break;
431
3/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
38 if (!first) ps.expect(',');
432 38 first = false;
433
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 3 times.
38 std::string name = ps.parse_string();
434
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 1 time.
35 ps.expect(':');
435
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 32 times.
34 if (name == "__metadata__") {
436
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 ps.skip_value();
437 2 continue;
438 }
439
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 4 times.
32 Entry e = parse_entry(ps);
440
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 2 times.
28 if (e.off_end < e.off_start || e.off_end > payload_size) {
441
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 throw std::runtime_error(
442
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 "safetensors: tensor '" + name + "' has out-of-range data_offsets");
443 }
444 26 TensorView tv;
445
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 tv.name = name;
446 26 tv.dtype = e.dtype;
447 26 tv.shape = std::move(e.shape);
448 26 tv.data = payload_base + e.off_start;
449 26 tv.nbytes = static_cast<std::size_t>(e.off_end - e.off_start);
450
451
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 1 time.
26 int64_t expected = tv.numel() * dtype_size_bytes(tv.dtype);
452
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 time.
25 if (expected != static_cast<int64_t>(tv.nbytes)) {
453
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw std::runtime_error(
454
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 "safetensors: tensor '" + name +
455 "' nbytes/shape mismatch");
456 }
457
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 f.index_.emplace(tv.name, f.tensors_.size());
458
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 f.tensors_.push_back(std::move(tv));
459 35 }
460 23 } catch (...) {
461 // f's destructor will unmap.
462 13 throw;
463 13 }
464 10 return f;
465 37 }
466
467 36 const TensorView* File::find(std::string_view name) const {
468 36 auto it = index_.find(name);
469
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 31 times.
36 if (it == index_.end()) return nullptr;
470 31 return &tensors_[it->second];
471 36 }
472
473 5 const TensorView& File::get(std::string_view name) const {
474 5 const TensorView* v = find(name);
475
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (!v) {
476
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 throw std::runtime_error(
477
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 "safetensors: no tensor named '" + std::string(name) + "'");
478 }
479 3 return *v;
480 2 }
481
482 // ─── Upload ────────────────────────────────────────────────────────────────
483
484 9 void upload(const TensorView& view, int rows, int cols, brotensor::Tensor& dst) {
485
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 if (rows <= 0 || cols <= 0) {
486
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 throw std::runtime_error("safetensors::upload: rows/cols must be positive");
487 }
488 7 const std::size_t expected =
489 14 static_cast<std::size_t>(rows) * static_cast<std::size_t>(cols) *
490 7 static_cast<std::size_t>(dtype_size_bytes(view.dtype));
491
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 time.
7 if (expected != view.nbytes) {
492
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 throw std::runtime_error(
493
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 "safetensors::upload: byte count mismatch for tensor '" + view.name + "'");
494 }
495
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (view.dtype == Dtype::F32) {
496 3 dst = brotensor::Tensor::from_host(reinterpret_cast<const float*>(view.data), rows, cols);
497
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
6 } else if (view.dtype == Dtype::F16) {
498 1 dst = brotensor::Tensor::from_host_fp16(
499 1 reinterpret_cast<const uint16_t*>(view.data), rows, cols);
500
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
3 } else if (view.dtype == Dtype::BF16) {
501 1 dst = brotensor::Tensor::from_host_bf16(
502 1 reinterpret_cast<const uint16_t*>(view.data), rows, cols);
503 1 } else {
504
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw std::runtime_error(
505
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 std::string("safetensors::upload: unsupported dtype ") +
506
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 dtype_name(view.dtype) + " for tensor '" + view.name + "'");
507 }
508 7 }
509
510 5 void upload_fp16(const TensorView& view, int rows, int cols, brotensor::Tensor& dst) {
511
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 if (rows <= 0 || cols <= 0) {
512
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
3 throw std::runtime_error("safetensors::upload_fp16: rows/cols must be positive");
513 }
514 4 const std::size_t n = static_cast<std::size_t>(rows) * static_cast<std::size_t>(cols);
515 4 const std::size_t expected = n * static_cast<std::size_t>(dtype_size_bytes(view.dtype));
516
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 if (expected != view.nbytes) {
517
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 throw std::runtime_error(
518
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 "safetensors::upload_fp16: byte count mismatch for tensor '" + view.name + "'");
519 }
520
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (view.dtype == Dtype::F16) {
521 1 dst = brotensor::Tensor::from_host_fp16(
522 1 reinterpret_cast<const uint16_t*>(view.data), rows, cols);
523
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
3 } else if (view.dtype == Dtype::F32) {
524 1 const float* src = reinterpret_cast<const float*>(view.data);
525 1 std::vector<uint16_t> tmp(n);
526
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6 times.
7 for (std::size_t i = 0; i < n; ++i) {
527
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 tmp[i] = brotensor::fp32_to_fp16_bits(src[i]);
528 6 }
529
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 dst = brotensor::Tensor::from_host_fp16(tmp.data(), rows, cols);
530 1 } else {
531
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw std::runtime_error(
532
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 std::string("safetensors::upload_fp16: unsupported dtype ") +
533
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 dtype_name(view.dtype) + " for tensor '" + view.name + "'");
534 }
535 4 }
536
537 21 void upload_as(const TensorView& view, int rows, int cols,
538 brotensor::Dtype want, brotensor::Tensor& dst) {
539
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 time.
21 if (rows <= 0 || cols <= 0) {
540
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
3 throw std::runtime_error("safetensors::upload_as: rows/cols must be positive");
541 }
542 20 const std::size_t n = static_cast<std::size_t>(rows) * static_cast<std::size_t>(cols);
543 20 const std::size_t expected = n * static_cast<std::size_t>(dtype_size_bytes(view.dtype));
544
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 time.
20 if (expected != view.nbytes) {
545
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 throw std::runtime_error(
546
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 "safetensors::upload_as: byte count mismatch for tensor '" + view.name + "'");
547 }
548
6/6
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 1 time.
19 if (view.dtype != Dtype::F16 && view.dtype != Dtype::F32 &&
549 8 view.dtype != Dtype::BF16) {
550
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw std::runtime_error(
551
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 std::string("safetensors::upload_as: unsupported source dtype ") +
552
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 dtype_name(view.dtype) + " for tensor '" + view.name + "'");
553 }
554
555 // Decode any source element to FP32.
556 18 const float* src32 = reinterpret_cast<const float*>(view.data);
557 18 const uint16_t* src16 = reinterpret_cast<const uint16_t*>(view.data);
558
559 // GPU fast path: when the on-disk dtype differs from the compute dtype
560 // (e.g. BF16 checkpoints with an FP16 compute policy), upload the raw bytes
561 // at the source dtype and cast on-device. The host convert-then-upload path
562 // below is a single-threaded scalar loop over every element — for a 2.6B-param
563 // encoder that is ~13 s, versus a ~0.1 s device cast after the same H2D copy.
564
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (brotensor::default_device() != brotensor::Device::CPU) {
565 brotensor::Tensor raw;
566 switch (view.dtype) {
567 case Dtype::F32: raw = brotensor::Tensor::from_host(src32, rows, cols); break;
568 case Dtype::F16: raw = brotensor::Tensor::from_host_fp16(src16, rows, cols); break;
569 default: raw = brotensor::Tensor::from_host_bf16(src16, rows, cols); break;
570 }
571 if (raw.dtype == want) {
572 dst = std::move(raw);
573 } else {
574 brotensor::cast(raw, dst, want);
575 }
576 return;
577 }
578 80 auto at_f32 = [&](std::size_t i) -> float {
579
3/3
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 12 times.
62 switch (view.dtype) {
580 12 case Dtype::F32: return src32[i];
581 26 case Dtype::F16: return brotensor::fp16_bits_to_fp32(src16[i]);
582 24 default: return brotensor::bf16_bits_to_fp32(src16[i]);
583 }
584 62 };
585
586
4/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 time.
18 switch (want) {
587 case brotensor::Dtype::FP32: {
588
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if (view.dtype == Dtype::F32) {
589 2 dst = brotensor::Tensor::from_host(src32, rows, cols);
590 2 return;
591 }
592 8 std::vector<float> tmp(n);
593
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
46 for (std::size_t i = 0; i < n; ++i) tmp[i] = at_f32(i);
594
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 dst = brotensor::Tensor::from_host(tmp.data(), rows, cols);
595 return;
596 8 }
597 case brotensor::Dtype::FP16: {
598
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (view.dtype == Dtype::F16) {
599 1 dst = brotensor::Tensor::from_host_fp16(src16, rows, cols);
600 1 return;
601 }
602 2 std::vector<uint16_t> tmp(n);
603
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 for (std::size_t i = 0; i < n; ++i) {
604
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 tmp[i] = brotensor::fp32_to_fp16_bits(at_f32(i));
605 12 }
606
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 dst = brotensor::Tensor::from_host_fp16(tmp.data(), rows, cols);
607 return;
608 2 }
609 case brotensor::Dtype::BF16: {
610
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (view.dtype == Dtype::BF16) {
611 2 dst = brotensor::Tensor::from_host_bf16(src16, rows, cols);
612 2 return;
613 }
614 2 std::vector<uint16_t> tmp(n);
615
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 for (std::size_t i = 0; i < n; ++i) {
616
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 tmp[i] = brotensor::fp32_to_bf16_bits(at_f32(i));
617 12 }
618
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 dst = brotensor::Tensor::from_host_bf16(tmp.data(), rows, cols);
619 return;
620 2 }
621 default:
622
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 throw std::runtime_error(
623 "safetensors::upload_as: target dtype must be FP32/FP16/BF16");
624 }
625 19 }
626
627 7 void upload_compute(const TensorView& view, int rows, int cols,
628 brotensor::Tensor& dst) {
629 // The compute dtype follows the active brotensor device: FP32 on the CPU
630 // backend, FP16 on a GPU backend. BF16 in particular is the on-disk dtype
631 // of Flux-family weights; CPU has no BF16 arithmetic, so it is widened to
632 // FP32 there rather than carried through as BF16.
633 7 upload_as(view, rows, cols, brotensor::compute_dtype(), dst);
634 7 }
635
636 4 void upload_compute_checked(const TensorView& view, int rows, int cols,
637 brotensor::Tensor& dst, const std::string& name) {
638
6/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
4 if (view.dtype != Dtype::F16 && view.dtype != Dtype::F32 &&
639 2 view.dtype != Dtype::BF16) {
640
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
3 throw std::runtime_error(
641
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
1 name + " ('" + view.name + "'): expected F16, F32, or BF16, got " +
642 1 dtype_name(view.dtype));
643 }
644 3 const int64_t expected =
645 3 static_cast<int64_t>(rows) * static_cast<int64_t>(cols);
646
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (view.numel() != expected) {
647
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 throw std::runtime_error(
648
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
2 name + " ('" + view.name + "'): shape mismatch (expected " +
649
6/12
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 time.
✗ Branch 11 not taken.
3 std::to_string(rows) + "x" + std::to_string(cols) + ", got " +
650
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 std::to_string(view.numel()) + " elements)");
651 }
652 2 upload_compute(view, rows, cols, dst);
653 4 }
654
655 // ─── Writer ────────────────────────────────────────────────────────────────
656
657 namespace {
658
659 19 const char* dtype_safetensors_name(Dtype d) {
660
7/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
19 switch (d) {
661 4 case Dtype::F32: return "F32";
662 1 case Dtype::F16: return "F16";
663 2 case Dtype::BF16: return "BF16";
664 1 case Dtype::I32: return "I32";
665 1 case Dtype::I64: return "I64";
666 9 case Dtype::U8: return "U8";
667 1 case Dtype::BOOL: return "BOOL";
668 default: throw std::runtime_error("safetensors::write: unsupported dtype");
669 }
670 19 }
671
672 19 void json_escape_append(std::string& out, const std::string& s) {
673
2/2
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 19 times.
133 for (char c : s) {
674
8/8
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 107 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 1 time.
114 switch (c) {
675 1 case '"': out += "\\\""; break;
676 1 case '\\': out += "\\\\"; break;
677 1 case '\n': out += "\\n"; break;
678 1 case '\t': out += "\\t"; break;
679 1 case '\r': out += "\\r"; break;
680 1 case '\b': out += "\\b"; break;
681 1 case '\f': out += "\\f"; break;
682 default:
683
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 106 times.
107 if (static_cast<unsigned char>(c) < 0x20) {
684 char buf[8];
685 2 std::snprintf(buf, sizeof(buf), "\\u%04x",
686 1 static_cast<unsigned>(static_cast<unsigned char>(c)));
687 1 out += buf;
688 1 } else {
689 106 out += c;
690 }
691 107 }
692 }
693 19 }
694
695 } // namespace
696
697 13 void write_file(const std::string& path, const std::vector<WriteEntry>& entries) {
698 // Validate + compute offsets.
699
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 std::vector<uint64_t> off_start(entries.size()), off_end(entries.size());
700 13 uint64_t cursor = 0;
701 13 std::unordered_set<std::string> seen_names;
702
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 6 times.
34 for (std::size_t i = 0; i < entries.size(); ++i) {
703 28 const WriteEntry& e = entries[i];
704
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 time.
28 if (e.name.empty()) {
705
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::runtime_error("safetensors::write: empty tensor name");
706 }
707
3/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 1 time.
27 if (!seen_names.insert(e.name).second) {
708
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
3 throw std::runtime_error("safetensors::write: duplicate tensor name '" +
709
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 e.name + "'");
710 }
711
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
26 if (!e.host_data && e.bytes > 0) {
712
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
1 throw std::runtime_error("safetensors::write: '" + e.name + "' null data");
713 }
714 25 int dsz = dtype_size_bytes(e.dtype);
715
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 2 times.
25 if (dsz <= 0) {
716
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 throw std::runtime_error("safetensors::write: '" + e.name + "' bad dtype");
717 }
718 // Same overflow guard the reader applies in TensorView::numel() — an
719 // unchecked product here is signed-overflow UB, not a clean throw.
720 23 int64_t n = 1;
721
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 22 times.
47 for (int64_t d : e.shape) {
722
6/10
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
25 if (d < 0) throw std::runtime_error("safetensors::write: '" + e.name + "' negative shape");
723
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
24 if (d != 0 && n > INT64_MAX / d)
724 throw std::runtime_error("safetensors::write: '" + e.name + "' element count overflows int64");
725 24 n *= d;
726 }
727 22 std::size_t expected = static_cast<std::size_t>(n) * static_cast<std::size_t>(dsz);
728
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 1 time.
22 if (expected != e.bytes) {
729
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
2 throw std::runtime_error("safetensors::write: '" + e.name +
730
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 "' byte count mismatch (shape=" + std::to_string(expected) +
731
3/6
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 " bytes=" + std::to_string(e.bytes) + ")");
732 }
733 21 off_start[i] = cursor;
734 21 cursor += e.bytes;
735 21 off_end[i] = cursor;
736 21 }
737
738 // Build JSON header.
739 6 std::string hdr;
740
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 hdr.reserve(256 * entries.size() + 16);
741
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 hdr += '{';
742
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 19 times.
25 for (std::size_t i = 0; i < entries.size(); ++i) {
743
3/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
19 if (i) hdr += ',';
744
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += '"';
745
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 json_escape_append(hdr, entries[i].name);
746
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += "\":{\"dtype\":\"";
747
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 hdr += dtype_safetensors_name(entries[i].dtype);
748
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += "\",\"shape\":[";
749
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 21 times.
40 for (std::size_t j = 0; j < entries[i].shape.size(); ++j) {
750
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
21 if (j) hdr += ',';
751
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21 hdr += std::to_string(entries[i].shape[j]);
752 21 }
753
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += "],\"data_offsets\":[";
754
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 hdr += std::to_string(off_start[i]);
755
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += ',';
756
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 hdr += std::to_string(off_end[i]);
757
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 hdr += "]}";
758 19 }
759
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 hdr += '}';
760 // Pad header to 8-byte alignment so payload tensor starts are aligned.
761
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
29 while ((hdr.size() % 8) != 0) hdr += ' ';
762
763 6 uint64_t header_size = static_cast<uint64_t>(hdr.size());
764
765 // Write file.
766 #ifdef _WIN32
767 FILE* fp = nullptr;
768 fopen_s(&fp, path.c_str(), "wb");
769 #else
770 // open(..., 0644) rather than fopen: fopen requests 0666 and leans on the
771 // caller's umask to take the write bits back off. A process that has
772 // cleared its umask — a daemon, a container entrypoint — then writes a
773 // world-writable checkpoint, which anyone on the box can swap under us.
774 // Ask for 0644 outright; umask can only ever narrow it further.
775
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 const int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
776
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
6 FILE* fp = (fd < 0) ? nullptr : ::fdopen(fd, "wb");
777
3/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (fd >= 0 && !fp) ::close(fd); // fdopen failed: the fd is still ours
778 #endif
779
6/10
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
6 if (!fp) throw std::runtime_error("safetensors::write: cannot open '" + path + "'");
780
781 5 auto wfail = [&](const std::string& what) {
782 std::fclose(fp);
783 throw std::runtime_error("safetensors::write: " + what + " '" + path + "'");
784 };
785
786
2/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
5 if (std::fwrite(&header_size, 1, 8, fp) != 8) wfail("header_size write failed");
787
2/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
5 if (std::fwrite(hdr.data(), 1, hdr.size(), fp) != hdr.size()) wfail("header write failed");
788
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 18 times.
23 for (const WriteEntry& e : entries) {
789
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 time.
18 if (e.bytes == 0) continue;
790
2/8
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
17 if (std::fwrite(e.host_data, 1, e.bytes, fp) != e.bytes) wfail("payload write failed");
791 }
792
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::fclose(fp);
793 20 }
794
795 } // namespace brotensor::safetensors
796